test: add recipe mode & component render

This commit is contained in:
YunYouJun
2022-06-26 07:42:53 +08:00
parent 33bf4b3a28
commit 31c8163ca9
15 changed files with 866 additions and 478 deletions

View File

@@ -47,3 +47,25 @@ jobs:
- name: Typecheck - name: Typecheck
run: pnpm run 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

View File

@@ -10,7 +10,7 @@
<meta name="description" content="好的,今天我们来做菜!"> <meta name="description" content="好的,今天我们来做菜!">
<title>隔离食用手册</title> <title>隔离食用手册</title>
<script> <script>
(function() { (function () {
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
const setting = localStorage.getItem('vueuse-color-scheme') || 'auto' const setting = localStorage.getItem('vueuse-color-scheme') || 'auto'
if (setting === 'dark' || (prefersDark && setting !== 'light')) if (setting === 'dark' || (prefersDark && setting !== 'light'))

View File

@@ -12,11 +12,12 @@
"postinstall": "npm run convert", "postinstall": "npm run convert",
"preview": "vite preview", "preview": "vite preview",
"preview-https": "serve dist", "preview-https": "serve dist",
"test": "vitest",
"typecheck": "vue-tsc --noEmit" "typecheck": "vue-tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@gtm-support/vue-gtm": "^1.6.0", "@gtm-support/vue-gtm": "^1.6.0",
"@vueuse/core": "^8.6.0", "@vueuse/core": "^8.7.5",
"@vueuse/head": "^0.7.6", "@vueuse/head": "^0.7.6",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"pinia": "^2.0.14", "pinia": "^2.0.14",
@@ -27,39 +28,42 @@
"vue-router": "^4.0.16" "vue-router": "^4.0.16"
}, },
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "^0.25.1", "@antfu/eslint-config": "^0.25.2",
"@iconify-json/fe": "^1.1.1", "@iconify-json/fe": "^1.1.1",
"@iconify-json/gg": "^1.1.1", "@iconify-json/gg": "^1.1.1",
"@iconify-json/ic": "^1.1.4", "@iconify-json/ic": "^1.1.5",
"@iconify-json/mdi": "^1.1.19", "@iconify-json/mdi": "^1.1.23",
"@iconify-json/ri": "^1.1.2", "@iconify-json/ri": "^1.1.2",
"@pinia/testing": "^0.0.12",
"@types/markdown-it-link-attributes": "^3.0.1", "@types/markdown-it-link-attributes": "^3.0.1",
"@types/nprogress": "^0.2.0", "@types/nprogress": "^0.2.0",
"@vitejs/plugin-vue": "^2.3.3", "@vitejs/plugin-vue": "^2.3.3",
"@vue/test-utils": "^2.0.0",
"consola": "^2.15.3", "consola": "^2.15.3",
"critters": "^0.0.16", "critters": "^0.0.16",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"eslint": "^8.17.0", "eslint": "^8.18.0",
"https-localhost": "^4.7.1", "https-localhost": "^4.7.1",
"markdown-it-link-attributes": "^4.0.0", "markdown-it-link-attributes": "^4.0.0",
"markdown-it-prism": "^2.2.4", "markdown-it-prism": "^2.2.4",
"pnpm": "^7.2.1", "pnpm": "^7.3.0",
"sass": "^1.52.3", "sass": "^1.53.0",
"star-markdown-css": "^0.3.3", "star-markdown-css": "^0.3.3",
"tsx": "^3.4.2", "tsx": "^3.6.0",
"typescript": "^4.7.3", "typescript": "^4.7.4",
"unocss": "^0.39.0", "unocss": "^0.41.0",
"unplugin-auto-import": "^0.7.2", "unplugin-auto-import": "^0.7.2",
"unplugin-vue-components": "^0.19.6", "unplugin-vue-components": "^0.19.9",
"vite": "^2.9.12", "vite": "^2.9.12",
"vite-plugin-inspect": "^0.5.0", "vite-plugin-inspect": "^0.5.0",
"vite-plugin-md": "^0.13.1", "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-pwa": "^0.12.0",
"vite-plugin-vue-layouts": "^0.6.0", "vite-plugin-vue-layouts": "^0.6.0",
"vite-ssg": "0.20.1", "vite-ssg": "0.20.1",
"vite-ssg-sitemap": "^0.2.7", "vite-ssg-sitemap": "^0.2.7",
"vitest": "^0.16.0",
"vue-toastification": "^2.0.0-rc.5", "vue-toastification": "^2.0.0-rc.5",
"vue-tsc": "^0.37.8" "vue-tsc": "^0.38.1"
} }
} }

1077
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -107,6 +107,7 @@ declare global {
const useAttrs: typeof import('vue')['useAttrs'] const useAttrs: typeof import('vue')['useAttrs']
const useBase64: typeof import('@vueuse/core')['useBase64'] const useBase64: typeof import('@vueuse/core')['useBase64']
const useBattery: typeof import('@vueuse/core')['useBattery'] const useBattery: typeof import('@vueuse/core')['useBattery']
const useBluetooth: typeof import('@vueuse/core')['useBluetooth']
const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints'] const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints']
const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel'] const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel']
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation'] const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
@@ -154,6 +155,7 @@ declare global {
const useGeolocation: typeof import('@vueuse/core')['useGeolocation'] const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
const useHead: typeof import('@vueuse/head')['useHead'] const useHead: typeof import('@vueuse/head')['useHead']
const useIdle: typeof import('@vueuse/core')['useIdle'] const useIdle: typeof import('@vueuse/core')['useIdle']
const useImage: typeof import('@vueuse/core')['useImage']
const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll'] const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']
const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver'] const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver']
const useInterval: typeof import('@vueuse/core')['useInterval'] const useInterval: typeof import('@vueuse/core')['useInterval']

2
src/components.d.ts vendored
View File

@@ -1,6 +1,6 @@
// generated by unplugin-vue-components // generated by unplugin-vue-components
// We suggest you to commit this file into source control // 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' import '@vue/runtime-core'
declare module '@vue/runtime-core' { declare module '@vue/runtime-core' {

View File

@@ -160,8 +160,9 @@ const randomRecipe = ref<RecipeItem>(generateRandomRecipe())
<ToggleMode /> <ToggleMode />
<!-- <Switch /> --> <!-- <Switch /> -->
<div p="2"> <div class="cook-recipes" p="2">
<Transition mode="out-in"> <Transition mode="out-in">
<div class="cook-filter-recipes">
<span v-if="!curStuff.length && !curTool" text="sm" p="2"> <span v-if="!curStuff.length && !curTool" text="sm" p="2">
你要先选食材或工具哦 你要先选食材或工具哦
</span> </span>
@@ -181,6 +182,7 @@ const randomRecipe = ref<RecipeItem>(generateRandomRecipe())
反馈新的菜谱 反馈新的菜谱
</span> </span>
</span> </span>
</div>
</Transition> </Transition>
<hr m="y-2"> <hr m="y-2">

View File

@@ -24,7 +24,7 @@ const triggerGtm = (val: string) => {
<template> <template>
<a <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" border="~ blue-200 dark:blue-800"
bg="blue-300 opacity-20" bg="blue-300 opacity-20"
@click="triggerGtm(dish.name)" @click="triggerGtm(dish.name)"

View File

@@ -6,7 +6,7 @@ defineProps<{
<template> <template>
<span <span
class="tag rounded" p="x-2" class="meat-tag tag rounded" p="x-2"
border="~ red-200 dark:red-800" border="~ red-200 dark:red-800"
:bg="active ? 'red-500 opacity-90' : 'red-300 opacity-20'" :bg="active ? 'red-500 opacity-90' : 'red-300 opacity-20'"
:text="active ? 'red-100' : 'red-800 dark:red-200'" :text="active ? 'red-100' : 'red-800 dark:red-200'"

View File

@@ -6,7 +6,7 @@ defineProps<{
<template> <template>
<span <span
class="tag rounded" p="x-2" class="vegetable-tag tag rounded" p="x-2"
border="~ green-200 dark:green-800" border="~ green-200 dark:green-800"
:bg="active ? 'green-600 opacity-90' : 'green-300 opacity-20'" :bg="active ? 'green-600 opacity-90' : 'green-300 opacity-20'"
:text="active ? 'green-100' : 'green-800 dark:green-200'" :text="active ? 'green-100' : 'green-800 dark:green-200'"

View File

@@ -54,6 +54,7 @@ export const useRecipeStore = defineStore('recipe', () => {
} }
return { return {
curStuff,
curTool, curTool,
curMode, curMode,
selectedStuff, selectedStuff,

55
test/component.test.ts Normal file
View 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
View 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)
})
})
})

View File

@@ -14,6 +14,7 @@
"strictNullChecks": true, "strictNullChecks": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"types": [ "types": [
"vitest",
"vite/client", "vite/client",
"vue/ref-macros", "vue/ref-macros",
"vite-plugin-pages/client", "vite-plugin-pages/client",

View File

@@ -119,4 +119,13 @@ export default defineConfig({
formatting: 'minify', formatting: 'minify',
onFinished() { generateSitemap() }, onFinished() { generateSitemap() },
}, },
// https://github.com/vitest-dev/vitest
test: {
include: ['test/**/*.test.ts'],
environment: 'jsdom',
deps: {
inline: ['@vue', '@vueuse', 'vue-demi'],
},
},
}) })