test work in progress

This commit is contained in:
2024-03-12 20:58:13 +01:00
parent 4b298fa332
commit 6dfd99dc88
11 changed files with 865 additions and 61 deletions

View File

@@ -11,7 +11,9 @@
"build:sw": "workbox generateSW workbox-config.js" "build:sw": "workbox generateSW workbox-config.js"
}, },
"dependencies": { "dependencies": {
"@faker-js/faker": "^8.4.1",
"@vee-validate/zod": "^4.12.5", "@vee-validate/zod": "^4.12.5",
"@vue/test-utils": "^2.4.4",
"@vueuse/core": "^10.9.0", "@vueuse/core": "^10.9.0",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.0", "clsx": "^2.1.0",
@@ -30,12 +32,15 @@
"devDependencies": { "devDependencies": {
"@types/node": "^20.11.24", "@types/node": "^20.11.24",
"@vitejs/plugin-vue": "^5.0.4", "@vitejs/plugin-vue": "^5.0.4",
"@vitest/ui": "^1.3.1",
"autoprefixer": "^10.4.18", "autoprefixer": "^10.4.18",
"jsdom": "^24.0.0",
"shadcn-vue": "^0.9.0", "shadcn-vue": "^0.9.0",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"vite": "^5.1.4", "vite": "^5.1.4",
"vite-plugin-pwa": "^0.19.0", "vite-plugin-pwa": "^0.19.0",
"vitest": "^1.3.1",
"vue-tsc": "^1.8.27" "vue-tsc": "^1.8.27"
} }
} }

714
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
import { faker } from "@faker-js/faker";
import { describe, expect, it } from "vitest";
import { mount } from '@vue/test-utils'
import EntryForm from './EntryForm.vue'
describe('Entry Form tests', () => {
it('takes in values for name and title and displays them in the respected fields', () => {
const name = faker.word.noun()
const text = faker.lorem.paragraph()
const component = mount(EntryForm, {
props: {
name, text, action: 'edit'
}
})
const nameField = component.find('input')
const textField = component.find('textarea')
console.log(nameField.element.classList, textField.text())
})
})

View File

@@ -1,14 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import * as z from 'zod' import * as z from 'zod'
import { toTypedSchema } from '@vee-validate/zod'
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Form, FormItem, FormLabel, FormField, FormControl, FormMessage, FormDescription } from '@/components/ui/form';
import { Button } from './ui/button'; import { Button } from './ui/button';
import { Textarea } from '@/components/ui/textarea'; import { Textarea } from '@/components/ui/textarea';
import { Entry } from '@/data/entries'; import { Entry } from '@/data/entries';
import { ref } from 'vue';
const emit = defineEmits<{ const emit = defineEmits<{
submit: [value: CreateEntrySchema] submit: [value: CreateEntrySchema]
}>() }>()
const props = defineProps<{ const props = defineProps<{
@@ -16,41 +15,38 @@ const props = defineProps<{
inputEntry?: Entry | undefined inputEntry?: Entry | undefined
}>() }>()
const nameField = ref(props.inputEntry ? props.inputEntry.name : '')
const textField = ref(props.inputEntry ? props.inputEntry.text : '')
const createEntryZodSchema = z.object({ const createEntrySchema = z.object({
name: z.string(), name: z.string(),
text: z.string().optional() text: z.string().optional()
}) })
export type CreateEntrySchema = z.infer<typeof createEntryZodSchema> export type CreateEntrySchema = z.infer<typeof createEntrySchema>
function submit() {
const result = createEntrySchema.safeParse({
name: nameField.value,
text: textField.value
})
if (result.success) {
emit('submit', result.data)
}
}
const createEntrySchema = toTypedSchema(createEntryZodSchema)
</script> </script>
<template> <template>
<Form :validation-schema="createEntrySchema" @submit="(val) => emit('submit', val as CreateEntrySchema)"> <form class="flex gap-3 flex-col">
<FormField v-slot="{ componentField }" name="name"> <Input placeholder="Name" v-model:model-value="nameField" />
<FormItem> <Textarea placeholder="Text" v-model:model-value="textField" />
<FormControl> <Button class="w-full" v-if="props.action === 'create'" @click="submit()">Create</Button>
<Input placeholder="Name" v-bind="componentField" :model-value="inputEntry ? inputEntry.name : ''" /> <Button class="w-full" v-else @click="submit()">Edit</Button>
</FormControl> </form>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ componentField }" name="text">
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Textarea placeholder="Text" v-bind="componentField" :model-value="inputEntry ? inputEntry.text : ''" />
</FormControl>
<FormDescription />
<FormMessage />
</FormItem>
</FormField>
<Button type="submit" class="w-full" v-if="props.action === 'create'">Create</Button>
<Button type="submit" class="w-full" v-else>Edit</Button>
</Form>
</template> </template>
<style scoped></style> <style scoped></style>

56
src/data/entries.test.ts Normal file
View File

@@ -0,0 +1,56 @@
import { expect, it } from "vitest"
import { describe } from "vitest"
import { faker } from '@faker-js/faker'
import { parseFromPossibleString } from "./entries"
import moment from "moment"
describe('function for managing entries data entity', () => {
function generateList<E>(length: number, generate: () => E) {
const arr: Array<E> = []
for (let i = 0; i < length; i++) {
arr.push(generate());
}
return arr;
}
function generateListWithWrongDate() {
return generateList(10, () => <any>{
name: faker.word.noun(),
text: faker.lorem.paragraph(),
last_reset: faker.word.sample()
})
}
function generateListWithNoName() {
return generateList(10, () => <any>{
text: faker.lorem.paragraph(),
last_reset: moment()
})
}
function generateListWithCorrectData() {
return generateList(10, () => <any>{
name: faker.word.noun(),
text: faker.lorem.paragraph(),
last_reset: moment()
})
}
it('fails to parse localstorage string because of wrong date', () => {
const data = generateListWithWrongDate()
expect(parseFromPossibleString(JSON.stringify(data)).length).toBe(0)
})
it('fails to parse localstorage string because no name was parsed', () => {
const data = generateListWithNoName()
expect(parseFromPossibleString(JSON.stringify(data)).length).toBe(0)
})
it('succeeds to parse localstorage string witht he correct data', () => {
const data = generateListWithCorrectData()
expect(parseFromPossibleString(JSON.stringify(data)).length).toBe(10)
})
})

View File

@@ -1,39 +1,46 @@
import moment, { Moment } from "moment"; import moment, { Moment } from "moment";
import { Ref, ref } from "vue";
const localStorageKey = 'entries' export const localStorageKey = 'entries'
export function getDifferenceToToday(date: Moment) { export function getDifferenceToToday(date: Moment) {
return Math.abs(date.diff(moment(), 'days')) return Math.abs(date.diff(moment(), 'days'))
} }
export const entries: Ref<Entry[]> = ref(parseFromPossibleString(localStorage.getItem(localStorageKey)))
export interface Entry { export interface Entry {
name: string name: string
text: string | undefined text: string | undefined
last_reset: Moment last_reset: Moment
} }
export function parseFromPossibleString(input: string | null): Entry[] { export function parseFromPossibleString(input: string | null): Entry[] {
if (input === null) { if (input === null) {
return [] return []
} }
const entries: Entry[] = [] const entries: Entry[] = []
const rawObjects: any[] = JSON.parse(input) const rawObjects: any[] = JSON.parse(input)
for (const rawObject of rawObjects) { for (const rawObject of rawObjects) {
const { name, text, last_reset } = rawObject const { name, text, last_reset } = rawObject
const date = parseDate(last_reset)
if (name && last_reset) { if (date && name) {
entries.push({ name, text, last_reset: moment(last_reset) }) entries.push({ name, text, last_reset: moment(last_reset) })
} }
}
return entries }
return entries
} }
export function save() { export function parseDate(dateString: string) {
localStorage.setItem(localStorageKey, JSON.stringify(entries.value)) const date = moment(dateString)
if (!date.isValid()) {
return undefined
}
return date
}
export function save(entries: Entry[]) {
localStorage.setItem(localStorageKey, JSON.stringify(entries))
} }

View File

@@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { Card, CardTitle, CardDescription } from '@/components/ui/card'; import { Card, CardTitle, CardDescription } from '@/components/ui/card';
import { entries, getDifferenceToToday, save } from '@/data/entries'; import { entries } from '@/state/entry';
import { getDifferenceToToday, save } from '@/data/entries';
import { ArrowBigLeft, MenuIcon, TrashIcon } from 'lucide-vue-next'; import { ArrowBigLeft, MenuIcon, TrashIcon } from 'lucide-vue-next';
import { import {
DropdownMenu, DropdownMenu,
@@ -40,7 +41,7 @@ function deleteEntry() {
} }
entries.value.splice(index, 1) entries.value.splice(index, 1)
save() save(entries.value)
router.back() router.back()
} }
@@ -52,7 +53,7 @@ function resetDate() {
entry.value.last_reset = moment() entry.value.last_reset = moment()
confirmDialogState.value = false confirmDialogState.value = false
save() save(entries.value)
} }
function editEntry(val: CreateEntrySchema) { function editEntry(val: CreateEntrySchema) {
@@ -65,7 +66,7 @@ function editEntry(val: CreateEntrySchema) {
entry.value.name = val.name entry.value.name = val.name
entry.value.text = val.text entry.value.text = val.text
editEntryDialog.value = false editEntryDialog.value = false
save() save(entries.value)
} }
</script> </script>

View File

@@ -1,7 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Plus } from 'lucide-vue-next'; import { Plus } from 'lucide-vue-next';
import { entries, save } from '@/data/entries'; import { entries } from '@/state/entry'
import { save } from '@/data/entries';
import { Drawer, DrawerHeader, DrawerTitle, DrawerContent } from '@/components/ui/drawer'; import { Drawer, DrawerHeader, DrawerTitle, DrawerContent } from '@/components/ui/drawer';
import { Dialog, DialogHeader, DialogTitle, DialogContent } from '@/components/ui/dialog'; import { Dialog, DialogHeader, DialogTitle, DialogContent } from '@/components/ui/dialog';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
@@ -36,7 +37,7 @@ async function createEntry(value: CreateEntrySchema) {
? value.text : undefined ? value.text : undefined
}) })
save() save(entries.value)
createDrawerState.value = false createDrawerState.value = false
} }

1
src/shims-vue.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
declare module '*.vue'

4
src/state/entry.ts Normal file
View File

@@ -0,0 +1,4 @@
import { Entry, localStorageKey, parseFromPossibleString } from "@/data/entries";
import { Ref, ref } from "vue";
export const entries: Ref<Entry[]> = ref(parseFromPossibleString(localStorage.getItem(localStorageKey)))

View File

@@ -1,4 +1,4 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vitest/config'
import { VitePWA } from 'vite-plugin-pwa' import { VitePWA } from 'vite-plugin-pwa'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import tailwind from 'tailwindcss' import tailwind from 'tailwindcss'
@@ -7,6 +7,10 @@ import path from 'path'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
test: {
environment: 'jsdom',
globals: true
},
plugins: [vue(), VitePWA({ plugins: [vue(), VitePWA({
registerType: 'autoUpdate', registerType: 'autoUpdate',
manifest: { manifest: {
@@ -27,4 +31,4 @@ export default defineConfig({
"@": path.resolve(__dirname, "./src"), "@": path.resolve(__dirname, "./src"),
}, },
}, },
}) })