test: add recipe mode & component render
This commit is contained in:
22
.github/workflows/ci.yml
vendored
22
.github/workflows/ci.yml
vendored
@@ -47,3 +47,25 @@ jobs:
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm run typecheck
|
||||
|
||||
test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [16.x]
|
||||
os: [ubuntu-latest]
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
registry-url: https://registry.npmjs.org/
|
||||
cache: pnpm
|
||||
|
||||
- run: pnpm install
|
||||
- run: pnpm run test
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<meta name="description" content="好的,今天我们来做菜!">
|
||||
<title>隔离食用手册</title>
|
||||
<script>
|
||||
(function() {
|
||||
(function () {
|
||||
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
const setting = localStorage.getItem('vueuse-color-scheme') || 'auto'
|
||||
if (setting === 'dark' || (prefersDark && setting !== 'light'))
|
||||
|
||||
30
package.json
30
package.json
@@ -12,11 +12,12 @@
|
||||
"postinstall": "npm run convert",
|
||||
"preview": "vite preview",
|
||||
"preview-https": "serve dist",
|
||||
"test": "vitest",
|
||||
"typecheck": "vue-tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@gtm-support/vue-gtm": "^1.6.0",
|
||||
"@vueuse/core": "^8.6.0",
|
||||
"@vueuse/core": "^8.7.5",
|
||||
"@vueuse/head": "^0.7.6",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.0.14",
|
||||
@@ -27,39 +28,42 @@
|
||||
"vue-router": "^4.0.16"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^0.25.1",
|
||||
"@antfu/eslint-config": "^0.25.2",
|
||||
"@iconify-json/fe": "^1.1.1",
|
||||
"@iconify-json/gg": "^1.1.1",
|
||||
"@iconify-json/ic": "^1.1.4",
|
||||
"@iconify-json/mdi": "^1.1.19",
|
||||
"@iconify-json/ic": "^1.1.5",
|
||||
"@iconify-json/mdi": "^1.1.23",
|
||||
"@iconify-json/ri": "^1.1.2",
|
||||
"@pinia/testing": "^0.0.12",
|
||||
"@types/markdown-it-link-attributes": "^3.0.1",
|
||||
"@types/nprogress": "^0.2.0",
|
||||
"@vitejs/plugin-vue": "^2.3.3",
|
||||
"@vue/test-utils": "^2.0.0",
|
||||
"consola": "^2.15.3",
|
||||
"critters": "^0.0.16",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.17.0",
|
||||
"eslint": "^8.18.0",
|
||||
"https-localhost": "^4.7.1",
|
||||
"markdown-it-link-attributes": "^4.0.0",
|
||||
"markdown-it-prism": "^2.2.4",
|
||||
"pnpm": "^7.2.1",
|
||||
"sass": "^1.52.3",
|
||||
"pnpm": "^7.3.0",
|
||||
"sass": "^1.53.0",
|
||||
"star-markdown-css": "^0.3.3",
|
||||
"tsx": "^3.4.2",
|
||||
"typescript": "^4.7.3",
|
||||
"unocss": "^0.39.0",
|
||||
"tsx": "^3.6.0",
|
||||
"typescript": "^4.7.4",
|
||||
"unocss": "^0.41.0",
|
||||
"unplugin-auto-import": "^0.7.2",
|
||||
"unplugin-vue-components": "^0.19.6",
|
||||
"unplugin-vue-components": "^0.19.9",
|
||||
"vite": "^2.9.12",
|
||||
"vite-plugin-inspect": "^0.5.0",
|
||||
"vite-plugin-md": "^0.13.1",
|
||||
"vite-plugin-pages": "^0.23.0",
|
||||
"vite-plugin-pages": "^0.24.2",
|
||||
"vite-plugin-pwa": "^0.12.0",
|
||||
"vite-plugin-vue-layouts": "^0.6.0",
|
||||
"vite-ssg": "0.20.1",
|
||||
"vite-ssg-sitemap": "^0.2.7",
|
||||
"vitest": "^0.16.0",
|
||||
"vue-toastification": "^2.0.0-rc.5",
|
||||
"vue-tsc": "^0.37.8"
|
||||
"vue-tsc": "^0.38.1"
|
||||
}
|
||||
}
|
||||
|
||||
1077
pnpm-lock.yaml
generated
1077
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
2
src/auto-imports.d.ts
vendored
2
src/auto-imports.d.ts
vendored
@@ -107,6 +107,7 @@ declare global {
|
||||
const useAttrs: typeof import('vue')['useAttrs']
|
||||
const useBase64: typeof import('@vueuse/core')['useBase64']
|
||||
const useBattery: typeof import('@vueuse/core')['useBattery']
|
||||
const useBluetooth: typeof import('@vueuse/core')['useBluetooth']
|
||||
const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints']
|
||||
const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel']
|
||||
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
|
||||
@@ -154,6 +155,7 @@ declare global {
|
||||
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
|
||||
const useHead: typeof import('@vueuse/head')['useHead']
|
||||
const useIdle: typeof import('@vueuse/core')['useIdle']
|
||||
const useImage: typeof import('@vueuse/core')['useImage']
|
||||
const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']
|
||||
const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver']
|
||||
const useInterval: typeof import('@vueuse/core')['useInterval']
|
||||
|
||||
2
src/components.d.ts
vendored
2
src/components.d.ts
vendored
@@ -1,6 +1,6 @@
|
||||
// generated by unplugin-vue-components
|
||||
// We suggest you to commit this file into source control
|
||||
// Read more: https://github.com/vuejs/vue-next/pull/3399
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
import '@vue/runtime-core'
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
|
||||
@@ -160,27 +160,29 @@ const randomRecipe = ref<RecipeItem>(generateRandomRecipe())
|
||||
<ToggleMode />
|
||||
|
||||
<!-- <Switch /> -->
|
||||
<div p="2">
|
||||
<div class="cook-recipes" p="2">
|
||||
<Transition mode="out-in">
|
||||
<span v-if="!curStuff.length && !curTool" text="sm" p="2">
|
||||
你要先选食材或工具哦~
|
||||
</span>
|
||||
|
||||
<span v-else-if="displayedRecipe.length">
|
||||
<DishTag v-for="item, i in displayedRecipe" :key="i" :dish="item" />
|
||||
</span>
|
||||
|
||||
<span v-else text="sm">
|
||||
还没有完美匹配的菜谱呢……
|
||||
<br>
|
||||
大胆尝试一下,或者<a href="#" @click="rStore.reset()">
|
||||
<strong>换个组合</strong></a>?
|
||||
<br>
|
||||
<span m="t-1">欢迎来
|
||||
<a class="font-bold text-blue-600 dark:text-blue-400" href="https://docs.qq.com/sheet/DQk1vdkhFV0twQVNS?tab=uykkic" target="_blank">这里</a>
|
||||
反馈新的菜谱!
|
||||
<div class="cook-filter-recipes">
|
||||
<span v-if="!curStuff.length && !curTool" text="sm" p="2">
|
||||
你要先选食材或工具哦~
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span v-else-if="displayedRecipe.length">
|
||||
<DishTag v-for="item, i in displayedRecipe" :key="i" :dish="item" />
|
||||
</span>
|
||||
|
||||
<span v-else text="sm">
|
||||
还没有完美匹配的菜谱呢……
|
||||
<br>
|
||||
大胆尝试一下,或者<a href="#" @click="rStore.reset()">
|
||||
<strong>换个组合</strong></a>?
|
||||
<br>
|
||||
<span m="t-1">欢迎来
|
||||
<a class="font-bold text-blue-600 dark:text-blue-400" href="https://docs.qq.com/sheet/DQk1vdkhFV0twQVNS?tab=uykkic" target="_blank">这里</a>
|
||||
反馈新的菜谱!
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<hr m="y-2">
|
||||
|
||||
@@ -24,7 +24,7 @@ const triggerGtm = (val: string) => {
|
||||
|
||||
<template>
|
||||
<a
|
||||
:href="dish.link || `https://www.bilibili.com/video/${dish.bv}`" target="_blank" class="tag rounded" p="x-2"
|
||||
:href="dish.link || `https://www.bilibili.com/video/${dish.bv}`" target="_blank" class="dish-tag tag rounded" p="x-2"
|
||||
border="~ blue-200 dark:blue-800"
|
||||
bg="blue-300 opacity-20"
|
||||
@click="triggerGtm(dish.name)"
|
||||
|
||||
@@ -6,7 +6,7 @@ defineProps<{
|
||||
|
||||
<template>
|
||||
<span
|
||||
class="tag rounded" p="x-2"
|
||||
class="meat-tag tag rounded" p="x-2"
|
||||
border="~ red-200 dark:red-800"
|
||||
:bg="active ? 'red-500 opacity-90' : 'red-300 opacity-20'"
|
||||
:text="active ? 'red-100' : 'red-800 dark:red-200'"
|
||||
|
||||
@@ -6,7 +6,7 @@ defineProps<{
|
||||
|
||||
<template>
|
||||
<span
|
||||
class="tag rounded" p="x-2"
|
||||
class="vegetable-tag tag rounded" p="x-2"
|
||||
border="~ green-200 dark:green-800"
|
||||
:bg="active ? 'green-600 opacity-90' : 'green-300 opacity-20'"
|
||||
:text="active ? 'green-100' : 'green-800 dark:green-200'"
|
||||
|
||||
@@ -54,6 +54,7 @@ export const useRecipeStore = defineStore('recipe', () => {
|
||||
}
|
||||
|
||||
return {
|
||||
curStuff,
|
||||
curTool,
|
||||
curMode,
|
||||
selectedStuff,
|
||||
|
||||
55
test/component.test.ts
Normal file
55
test/component.test.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import ChooseFood from '../src/components/ChooseFood.vue'
|
||||
import { useRecipeStore } from '~/stores/recipe'
|
||||
|
||||
describe('ChooseFood.vue', () => {
|
||||
it('should render', async () => {
|
||||
const pinia = createTestingPinia({
|
||||
createSpy: vi.fn,
|
||||
})
|
||||
const wrapper = mount(ChooseFood, {
|
||||
global: {
|
||||
plugins: [
|
||||
pinia,
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const rStore = useRecipeStore()
|
||||
|
||||
rStore.curStuff.add('黄瓜')
|
||||
|
||||
expect(wrapper.find('.vegetable-tag').exists()).toBe(true)
|
||||
expect(wrapper.find('.cook-filter-recipes').exists()).toBe(true)
|
||||
|
||||
await wrapper.find('.vegetable-tag').trigger('click')
|
||||
|
||||
const tags = wrapper.find('.cook-filter-recipes').findAll('.dish-tag')
|
||||
|
||||
expect(tags.length > 0).toBe(true)
|
||||
|
||||
tags.forEach((tag) => {
|
||||
const result = tag.text().includes('🥒') || tag.text().includes('🍲')
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
it('should be interactive', async () => {
|
||||
// const wrapper = mount(ChooseFood)
|
||||
// expect(wrapper.text()).toContain('0')
|
||||
|
||||
// expect(wrapper.find('.inc').exists()).toBe(true)
|
||||
|
||||
// expect(wrapper.find('.dec').exists()).toBe(true)
|
||||
|
||||
// await wrapper.get('.inc').trigger('click')
|
||||
|
||||
// expect(wrapper.text()).toContain('1')
|
||||
|
||||
// await wrapper.get('.dec').trigger('click')
|
||||
|
||||
// expect(wrapper.text()).toContain('0')
|
||||
})
|
||||
})
|
||||
97
test/recipe.test.ts
Normal file
97
test/recipe.test.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { useRecipe } from '~/composables/recipe'
|
||||
import type { Recipe } from '~/types'
|
||||
import recipeData from '~/data/recipe.json'
|
||||
import { useRecipeStore } from '~/stores/recipe'
|
||||
|
||||
const recipe = ref<Recipe>(recipeData as Recipe)
|
||||
|
||||
describe('recipe interaction', () => {
|
||||
beforeEach(() => {
|
||||
// creates a fresh pinia and make it active so it's automatically picked
|
||||
// up by any useStore() call without having to pass it to it:
|
||||
// `useStore(pinia)`
|
||||
setActivePinia(createPinia())
|
||||
})
|
||||
|
||||
it('toggle stuff', () => {
|
||||
const rStore = useRecipeStore()
|
||||
rStore.toggleStuff('土豆')
|
||||
expect(rStore.selectedStuff.includes('土豆')).toBe(true)
|
||||
rStore.toggleStuff('土豆')
|
||||
expect(rStore.selectedStuff.includes('土豆')).toBe(false)
|
||||
})
|
||||
|
||||
it('toggle tool', () => {
|
||||
const rStore = useRecipeStore()
|
||||
rStore.toggleTools('电饭煲')
|
||||
expect(rStore.curTool === '电饭煲').toBe(true)
|
||||
rStore.toggleTools('微波炉')
|
||||
expect(rStore.curTool === '微波炉').toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('recipe mode', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
})
|
||||
|
||||
it('loose mode', () => {
|
||||
const rStore = useRecipeStore()
|
||||
const { displayedRecipe } = useRecipe(recipe)
|
||||
|
||||
rStore.curStuff.clear()
|
||||
rStore.curStuff.add('土豆')
|
||||
rStore.curStuff.add('腊肠')
|
||||
|
||||
rStore.curTool = '电饭煲'
|
||||
|
||||
expect(rStore.selectedStuff).toStrictEqual(['土豆', '腊肠'])
|
||||
rStore.setMode('strict')
|
||||
|
||||
displayedRecipe.value.forEach((item) => {
|
||||
expect(item.stuff.includes('土豆') || item.stuff.includes('腊肠')).toBe(true)
|
||||
expect(item.tools?.includes('电饭煲')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
it('strict mode', () => {
|
||||
const rStore = useRecipeStore()
|
||||
const { displayedRecipe } = useRecipe(recipe)
|
||||
|
||||
rStore.curStuff.clear()
|
||||
rStore.curStuff.add('土豆')
|
||||
rStore.curStuff.add('腊肠')
|
||||
|
||||
rStore.curTool = '电饭煲'
|
||||
|
||||
expect(rStore.selectedStuff).toStrictEqual(['土豆', '腊肠'])
|
||||
rStore.setMode('strict')
|
||||
|
||||
displayedRecipe.value.forEach((item) => {
|
||||
expect(item.stuff.includes('土豆') && item.stuff.includes('腊肠')).toBe(true)
|
||||
expect(item.tools?.includes('电饭煲')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
it('survival mode', () => {
|
||||
const rStore = useRecipeStore()
|
||||
const { displayedRecipe } = useRecipe(recipe)
|
||||
|
||||
rStore.curStuff.clear()
|
||||
rStore.curStuff.add('土豆')
|
||||
rStore.curStuff.add('腊肠')
|
||||
|
||||
expect(rStore.selectedStuff).toStrictEqual(['土豆', '腊肠'])
|
||||
rStore.setMode('survival')
|
||||
|
||||
displayedRecipe.value.forEach((item) => {
|
||||
let canCook = false
|
||||
item.stuff.forEach((stuff) => {
|
||||
canCook = canCook && rStore.selectedStuff.includes(stuff)
|
||||
})
|
||||
expect(canCook).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -14,6 +14,7 @@
|
||||
"strictNullChecks": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"types": [
|
||||
"vitest",
|
||||
"vite/client",
|
||||
"vue/ref-macros",
|
||||
"vite-plugin-pages/client",
|
||||
|
||||
@@ -119,4 +119,13 @@ export default defineConfig({
|
||||
formatting: 'minify',
|
||||
onFinished() { generateSitemap() },
|
||||
},
|
||||
|
||||
// https://github.com/vitest-dev/vitest
|
||||
test: {
|
||||
include: ['test/**/*.test.ts'],
|
||||
environment: 'jsdom',
|
||||
deps: {
|
||||
inline: ['@vue', '@vueuse', 'vue-demi'],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user