diff --git a/.gitignore b/.gitignore index 6a7a23dc..04922386 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ node_modules !.env.example vite.config.js.timestamp-* vite.config.ts.timestamp-* +coverage/ /web-components diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a6bdba4..fdaebc9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# v0.13.0 + +- Add support for external entities + # v0.12.0 - **BC Break**: CrudDefinition's `name` is no longer an argument, and set in the options instead. diff --git a/package.json b/package.json index dcac4f44..38b7850c 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "@types/uuid": "^9.0.7", "@typescript-eslint/eslint-plugin": "^6.13.2", "@typescript-eslint/parser": "^6.13.2", + "@vitest/coverage-v8": "^1.2.2", "axios": "^1.6.2", "eslint": "^8.55.0", "eslint-config-prettier": "^9.1.0", diff --git a/src/lib/Crud/Operations.ts b/src/lib/Crud/Operations.ts index 4fed07d4..76a52973 100644 --- a/src/lib/Crud/Operations.ts +++ b/src/lib/Crud/Operations.ts @@ -6,7 +6,15 @@ import type { CrudTheme } from '$lib/themes/ThemeConfig'; import { defaultPaginationOptions, type PaginationOptions } from '$lib/DataTable/Pagination'; import type { FilterInterface, FilterOptions } from '$lib/Filter'; -export type CrudOperationName = 'new' | 'edit' | 'view' | 'list' | 'delete' | string; +export type CrudOperationName = + | 'new' + | 'edit' + | 'view' + | 'list' + | 'delete' + | 'entity_view' + | 'entity_list' + | string; export interface CrudOperation { readonly name: CrudOperationName; @@ -126,3 +134,9 @@ export class View extends BaseCrudOperation { super('view', 'crud.view.label', 'view', fields, []); } } + +export class Field extends BaseCrudOperation { + constructor(name: CrudOperationName = 'field', options: Record = {}) { + super(name, '', 'field', [], [], options); + } +} diff --git a/src/lib/Dashboard/index.test.ts b/src/lib/Dashboard/index.test.ts index b153d5ce..2fbd454c 100644 --- a/src/lib/Dashboard/index.test.ts +++ b/src/lib/Dashboard/index.test.ts @@ -17,10 +17,11 @@ describe( 'Dashboard', () => { it('can be instantiated with simple config', () => { - const dashboard = new DashboardDefinition({ + const dashboard = new DashboardDefinition({ adminConfig: {}, cruds: [ - new CrudDefinition('books', { + new CrudDefinition({ + name: 'books', label: { singular: 'Book', plural: 'Books' }, operations: [new List([])], stateProvider: new CallbackStateProvider(() => Promise.resolve(null)), diff --git a/src/lib/DataTable/DataTable.ts b/src/lib/DataTable/DataTable.ts index ecf51330..7fca0856 100644 --- a/src/lib/DataTable/DataTable.ts +++ b/src/lib/DataTable/DataTable.ts @@ -4,8 +4,6 @@ import type { } from 'carbon-components-svelte/types/DataTable/DataTable.svelte'; import type { CrudOperation } from '$lib/Crud/Operations'; -import type { CommonFieldOptions, Field } from '$lib/FieldDefinitions/definition'; -import type { KeyValueObject } from '$lib/genericTypes'; export type Header = DataTableHeader; export type Headers = Array
; @@ -14,17 +12,3 @@ export type Row = DataTableRow & { __operation: CrudOperation; }; export type Rows = Array; - -export function createEmptyRow(operation: CrudOperation): Row { - const fields: KeyValueObject = {}; - - operation.fields.forEach((field: Field) => { - fields[field.name] = '-'; - }); - - return { - id: 0, - __operation: operation, - ...fields - }; -} diff --git a/src/lib/DataTable/index.test.ts b/src/lib/DataTable/index.test.ts index e876f811..82d1ae05 100644 --- a/src/lib/DataTable/index.test.ts +++ b/src/lib/DataTable/index.test.ts @@ -1,5 +1,4 @@ import { describe, it, expect, type TestOptions } from 'vitest'; -import { createEmptyRow } from '$lib/DataTable/DataTable'; import { List, CheckboxField, @@ -20,43 +19,9 @@ const testOpts: TestOptions = { describe( 'DataTable', () => { - it('can create an empty row from empty CrudOperation', () => { - const row = createEmptyRow(new List([])); - - expect(row).toBeDefined(); - expect(row).toStrictEqual({ - id: 0 - }); - }); - - it('can create an empty row from CrudOperation with many text-based fields', () => { - const fields = [ - new CheckboxField('field_1'), - new Field('field_2'), - new NumberField('field_3'), - new TextareaField('field_4'), - new TextField('field_5'), - new ToggleField('field_6'), - new UrlField('field_7') - ]; - const row = createEmptyRow(new List(fields)); - - expect(row).toBeDefined(); - expect(row).toStrictEqual({ - id: 0, - field_1: '-', - field_2: '-', - field_3: '-', - field_4: '-', - field_5: '-', - field_6: '-', - field_7: '-' - }); - }); - it('can create a Tabs configuration with 1 level of nested fields', () => { const fields = [ - new Tabs([ + new Tabs('', '', [ { name: 'tab_1', fields: [new CheckboxField('field_1')] }, { name: 'tab_2', fields: [new Field('field_2')] }, { name: 'tab_3', fields: [new NumberField('field_3')] }, @@ -73,7 +38,7 @@ describe( it('can create a Columns configuration with 1 level of nested fields', () => { const fields = [ - new Columns([ + new Columns('', '', [ { name: 'column_1', fields: [new CheckboxField('field_1')] }, { name: 'column_2', fields: [new Field('field_2')] }, { name: 'column_3', fields: [new NumberField('field_3')] }, diff --git a/src/lib/FieldDefinitions/CrudEntity.ts b/src/lib/FieldDefinitions/CrudEntity.ts new file mode 100644 index 00000000..dbb01a34 --- /dev/null +++ b/src/lib/FieldDefinitions/CrudEntity.ts @@ -0,0 +1,27 @@ +import { Field, type InputFieldOptions } from '$lib/FieldDefinitions/definition'; +import type { FormFieldTheme, ViewFieldTheme } from '$lib/themes/ThemeConfig'; +import type { KeyValueObject } from '$lib/genericTypes'; + +export type CrudEntityOptions = InputFieldOptions & { + crud_name: string; + list_provider_operation?: { + name?: 'entity_list' | string; + options?: KeyValueObject; + label_field: string; + value_field?: 'id' | string; + }; + get_provider_operation: { + name?: 'entity_view' | string; + options?: KeyValueObject; + entity_field: string; + }; +}; + +export class CrudEntityField extends Field { + readonly formComponent: FormFieldTheme = 'crud_entity'; + readonly viewComponent: ViewFieldTheme = 'crud_entity'; + + constructor(name: string, label: string = '', options: CrudEntityOptions) { + super(name, label, options); + } +} diff --git a/src/lib/actions.test.ts b/src/lib/actions.test.ts new file mode 100644 index 00000000..da66e2a1 --- /dev/null +++ b/src/lib/actions.test.ts @@ -0,0 +1,77 @@ +import { describe, it, expect, type TestOptions } from 'vitest'; +import { CallbackAction, UrlAction } from '$lib/actions'; + +const testOpts: TestOptions = { + repeats: process.env.REPEAT ? parseInt(process.env.REPEAT) : undefined +}; + +describe( + 'URL actions', + () => { + it('handles item fields in specified request parameters', () => { + const action = new UrlAction('', '/test/:field1/:field2'); + const item = { + field1: 'val1', + field2: 'val2', + field3: 'val3' + }; + + expect(action.url(item)).toBe('/test/val1/val2'); + }); + + it('handles item ID with default "id" when not specified', () => { + const action = new UrlAction('', '/test/:field1'); + const item = { + id: 'identifier', + field1: 'val1' + }; + + expect(action.url(item)).toBe('/test/val1?id=identifier'); + }); + + it('handles item ID with custom "id" property', () => { + const action = new UrlAction('', '/test/:field1'); + const item = { + customId: 'custom_identifier', + field1: 'val1' + }; + + expect(action.url(item, 'customId')).toBe('/test/val1?id=custom_identifier'); + }); + }, + testOpts +); + +describe( + 'Callback actions', + () => { + it('calls function', () => { + let called = false; + const callback = () => { + called = true; + return called; + }; + const action = new CallbackAction('', null, callback); + + expect(action.call()).toBe(true); + expect(called).toBe(true); + }); + + it('calls function with item as argument', () => { + let called = false; + const baseItem = { + field: 'value' + }; + const callback = (item: typeof baseItem): boolean => { + called = true; + item.field = 'newValue'; + return called; + }; + const action = new CallbackAction('', null, callback); + + expect(action.call(baseItem)).toBe(true); + expect(baseItem.field).toBe('newValue'); + }); + }, + testOpts +); diff --git a/src/lib/actions.ts b/src/lib/actions.ts index afefe6d6..2c9d3e54 100644 --- a/src/lib/actions.ts +++ b/src/lib/actions.ts @@ -49,7 +49,7 @@ export class CallbackAction extends DefaultAction { this._callback = callback; } - public call(item?: object | undefined): void { + public call(item?: object | undefined): unknown { return this._callback.call(null, item); } } diff --git a/src/lib/themes/ThemeConfig.ts b/src/lib/themes/ThemeConfig.ts index 46cf7ca9..0c13308e 100644 --- a/src/lib/themes/ThemeConfig.ts +++ b/src/lib/themes/ThemeConfig.ts @@ -18,6 +18,7 @@ export type ThemeConfig = { viewFields: { checkbox: ComponentType; column: ComponentType; + crud_entity: ComponentType; date: ComponentType; default: ComponentType; label: ComponentType; @@ -33,6 +34,7 @@ export type ThemeConfig = { formFields: { checkbox: ComponentType; column: ComponentType; + crud_entity: ComponentType; date: ComponentType; default: ComponentType; number: ComponentType; diff --git a/src/lib/themes/carbon/Crud/CrudList.svelte b/src/lib/themes/carbon/Crud/CrudList.svelte index e51518a9..fbcfb1ed 100644 --- a/src/lib/themes/carbon/Crud/CrudList.svelte +++ b/src/lib/themes/carbon/Crud/CrudList.svelte @@ -3,7 +3,7 @@ import { onMount } from 'svelte'; import Pagination from 'carbon-components-svelte/src/Pagination/Pagination.svelte'; - import { createEmptyRow, type Header, type Headers } from '$lib/DataTable/DataTable'; + import { type Header, type Headers } from '$lib/DataTable/DataTable'; import type { Field, FieldOptions } from '$lib/FieldDefinitions/definition'; import type { CrudDefinition } from '$lib/Crud/definition'; diff --git a/src/lib/themes/carbon/DataTable/DataTable.svelte b/src/lib/themes/carbon/DataTable/DataTable.svelte index 5a86311d..87f19ad5 100644 --- a/src/lib/themes/carbon/DataTable/DataTable.svelte +++ b/src/lib/themes/carbon/DataTable/DataTable.svelte @@ -120,6 +120,7 @@ field={getFieldFromRow(cell.key, row)} value={cell.display ? cell.display(cell.value) : cell.value} operation={$$restProps.operation} + entityObject={row} {theme} > {cell.display ? cell.display(cell.value) : cell.value} @@ -146,7 +147,7 @@ - {$_('error.crud.list.load_error')}
+ {$_('error.crud.list.load_error')}
{error.toString()}
diff --git a/src/lib/themes/carbon/FormFieldsComponents/CrudEntityField.svelte b/src/lib/themes/carbon/FormFieldsComponents/CrudEntityField.svelte new file mode 100644 index 00000000..e8def120 --- /dev/null +++ b/src/lib/themes/carbon/FormFieldsComponents/CrudEntityField.svelte @@ -0,0 +1,61 @@ + + +{#if !crud} + + {$_('error.crud.could_not_find_crud_name', { values: { crud: field.options.crud_name } })} + +{:else} + {#await fetchList()} + + {:then data} + {@const values = data} + + {:catch error} + + {$_('error.crud.form.entity_field_list_fetch_error', { values: { message: error.message } })} + + {/await} +{/if} diff --git a/src/lib/themes/carbon/FormFieldsComponents/KeyValueObjectField.svelte b/src/lib/themes/carbon/FormFieldsComponents/KeyValueObjectField.svelte index 49cd4597..975d2fb8 100644 --- a/src/lib/themes/carbon/FormFieldsComponents/KeyValueObjectField.svelte +++ b/src/lib/themes/carbon/FormFieldsComponents/KeyValueObjectField.svelte @@ -20,7 +20,7 @@ throw new Error('Value was expected to be an object, but "' + typeof value + '" given.'); } if (!value) { - value = {'': ''}; + value = { '': '' }; } let valueEntries = Object.entries(value); diff --git a/src/lib/themes/carbon/ViewFieldsComponents/CrudEntityField.svelte b/src/lib/themes/carbon/ViewFieldsComponents/CrudEntityField.svelte new file mode 100644 index 00000000..1569b899 --- /dev/null +++ b/src/lib/themes/carbon/ViewFieldsComponents/CrudEntityField.svelte @@ -0,0 +1,51 @@ + + +{#if !crud} + + {$_('error.crud.could_not_find_crud_name', { values: { crud: field.options.crud_name } })} + +{:else} + {#await fetchData()} + + {:then data} + {data[field.options.get_provider_operation.entity_field]} + {:catch error} + + {$_('error.crud.form.entity_field_view_fetch_error', { values: { message: error.message } })} + + {/await} +{/if} diff --git a/src/lib/themes/carbon/index.js b/src/lib/themes/carbon/index.js index c0fcdda8..bb3a3ad6 100644 --- a/src/lib/themes/carbon/index.js +++ b/src/lib/themes/carbon/index.js @@ -13,6 +13,7 @@ import DataTable from './DataTable/DataTable.svelte'; import CheckboxFormField from './FormFieldsComponents/CheckboxField.svelte'; import ColumnsFormField from './FormFieldsComponents/ColumnsField.svelte'; +import CrudEntityFormField from './FormFieldsComponents/CrudEntityField.svelte'; import DateFormField from './FormFieldsComponents/DateField.svelte'; import DefaultFormField from './FormFieldsComponents/DefaultField.svelte'; import NumberFormField from './FormFieldsComponents/NumberField.svelte'; @@ -32,6 +33,7 @@ import TopRightMenu from './Menu/TopRightMenu.svelte'; import CheckboxViewField from './ViewFieldsComponents/CheckboxField.svelte'; import ColumnsViewField from './ViewFieldsComponents/ColumnsField.svelte'; +import CrudEntityViewField from './ViewFieldsComponents/CrudEntityField.svelte'; import DateViewField from './ViewFieldsComponents/DateField.svelte'; import DefaultViewField from './ViewFieldsComponents/DefaultField.svelte'; import NumberViewField from './ViewFieldsComponents/NumberField.svelte'; @@ -69,6 +71,7 @@ const theme = { viewFields: { checkbox: CheckboxViewField, column: ColumnsViewField, + crud_entity: CrudEntityViewField, date: DateViewField, default: DefaultViewField, label: ViewLabel, @@ -83,6 +86,7 @@ const theme = { formFields: { checkbox: CheckboxFormField, column: ColumnsFormField, + crud_entity: CrudEntityFormField, date: DateFormField, default: DefaultFormField, number: NumberFormField, diff --git a/src/lib/translations/en.ts b/src/lib/translations/en.ts index c4a0a129..c03bcf5b 100644 --- a/src/lib/translations/en.ts +++ b/src/lib/translations/en.ts @@ -13,6 +13,10 @@ const dictionary: Dictionary = { 'crud.delete.cancel': 'No, cancel', 'crud.delete.yes_delete': 'Yes, delete', 'data_table.items.unsupported_action': 'Action type "{action}" not supported.', + 'error.crud.form.entity_field_list_fetch_error': + 'An error occurred while fetching a list of elements:\n{message}', + 'error.crud.form.entity_field_view_fetch_error': + 'An error occurred while fetching an element:\n{message}', 'error.crud.form.object.duplicate_key': '⚠ Key already exists!', 'error.crud.could_not_find_crud_name': 'Could not find a CRUD config with name "{crud}".', 'error.crud.no_crud_specified': 'No CRUD name was specified when displaying the Dashboard.', diff --git a/src/lib/translations/fr.ts b/src/lib/translations/fr.ts index ba0fdca6..b39ddf07 100644 --- a/src/lib/translations/fr.ts +++ b/src/lib/translations/fr.ts @@ -13,6 +13,10 @@ const dictionary: Dictionary = { 'crud.delete.cancel': 'Non, annuler', 'crud.delete.yes_delete': 'Oui, supprimer', 'data_table.items.unsupported_action': 'Type d\'action "{operation}" non prise en charge.', + 'error.crud.form.entity_field_list_fetch_error': + "Une erreur est survenue lors de la récupération d'une liste d'élements:\n{message}", + 'error.crud.form.entity_field_view_fetch_error': + "Une erreur est survenue lors de la récupération d'un élement:\n{message}", 'error.crud.form.object.duplicate_key': '⚠ Clé déjà présente !', 'error.crud.could_not_find_crud_name': 'Configuration de CRUD "{crud}" introuvable.', 'error.crud.no_crud_specified': diff --git a/src/testApp/BookCrud.ts b/src/testApp/BookCrud.ts index d3b43a2f..abcca63c 100644 --- a/src/testApp/BookCrud.ts +++ b/src/testApp/BookCrud.ts @@ -89,10 +89,12 @@ export const bookCrud = new CrudDefinition({ operation, requestParameters: RequestParameters = {} ) { + const books = getMemoryBooks(); + if (operation.name === 'delete') { const id = (requestParameters.id || '').toString(); getBook(id); - const updatedBooks = getMemoryBooks().filter((b) => b.id.toString() !== id); + const updatedBooks = books.filter((b) => b.id.toString() !== id); window.localStorage.setItem('books', JSON.stringify(updatedBooks)); return Promise.resolve(); @@ -103,7 +105,7 @@ export const bookCrud = new CrudDefinition({ operation.name === 'edit' ? (requestParameters.id || '').toString() : faker.string.uuid(); const book = data as Book; book.id = id; - let updatedBooks = getMemoryBooks(); + let updatedBooks = books; if (operation.name === 'new') { updatedBooks.push(book); @@ -116,6 +118,8 @@ export const bookCrud = new CrudDefinition({ return Promise.resolve(); } + console.error('StateProcessor error: Unsupported Books Crud action "' + operation.name + '".'); + return Promise.resolve(); }), @@ -161,7 +165,7 @@ export const bookCrud = new CrudDefinition({ ); } - if (requestParameters.id !== undefined) { + if (operation.name === 'edit' || operation.name === 'view') { const ret = books.filter( (book: { id: string | number }) => book.id && book.id.toString() === requestParameters.id ); @@ -169,10 +173,21 @@ export const bookCrud = new CrudDefinition({ return Promise.resolve(ret[0] || null); } - if (operation.name !== 'edit' && operation.name !== 'view' && operation.name !== 'delete') { - console.warn('StateProvider error: Unsupported Books Crud action "' + operation.name + '".'); + if (operation.name === 'entity_view') { + const ret = books.filter( + (book: { id: string | number }) => + book.id && book.id.toString() === requestParameters.field_value + ); + + return Promise.resolve(ret[0] || null); + } + + if (operation.name === 'entity_list') { + return Promise.resolve(books); } + console.error('StateProvider error: Unsupported Books Crud action "' + operation.name + '".'); + return Promise.resolve(null); }) }); diff --git a/src/testApp/TestCrud.ts b/src/testApp/TestCrud.ts index f177eb7b..d35310f5 100644 --- a/src/testApp/TestCrud.ts +++ b/src/testApp/TestCrud.ts @@ -37,6 +37,7 @@ import type { FieldInterface, FieldOptions } from '$lib/FieldDefinitions/definit import type { RequestParameters } from '$lib/request'; import { type Test, getMemoryTests } from './internal/testsInternal'; +import { CrudEntityField } from '$lib/FieldDefinitions/CrudEntity'; const itemsPerPage = 10; @@ -45,21 +46,31 @@ const baseFields: Array> = [ placeholder: 'This is a placeholder', help: 'This is a help message!' }), - new TextareaField('textarea_field', 'Textarea description', { - placeholder: 'This is also a placeholder', - help: 'This is a help message!' - }), new CheckboxField('checkbox_field', 'Checkbox field'), new NumberField('number_field', 'Number field'), new ToggleField('toggle_field', 'Toggle field'), new UrlField('url_field', 'Url field', { openInNewTab: true }), new UrlField('path_field', 'Path-URL field'), new DateField('date_field', 'Date field'), + new CrudEntityField('crud_entity_field', 'Crud entity field (book)', { + crud_name: 'books', + get_provider_operation: { + entity_field: 'title' + }, + list_provider_operation: { + label_field: 'title', + value_field: 'id' + } + }), new KeyValueObjectField('key_value_object_field', 'Key Value Object field', 'data1') ]; const fullFields = [ ...baseFields, + new TextareaField('textarea_field', 'Textarea description', { + placeholder: 'This is also a placeholder', + help: 'This is a help message!' + }), new Columns('columns_field', 'Columns field', [ { name: 'column_1', @@ -154,7 +165,7 @@ export const testCrud = new CrudDefinition({ ); } - if (requestParameters.id !== undefined) { + if (operation.name === 'edit' || operation.name === 'view') { const ret = tests.filter( (test: { id: string | number }) => test.id && test.id.toString() === requestParameters.id ); @@ -162,9 +173,7 @@ export const testCrud = new CrudDefinition({ return Promise.resolve(ret[0] || null); } - if (operation.name !== 'edit' && operation.name !== 'view' && operation.name !== 'delete') { - console.warn('StateProvider error: Unsupported Tests Crud action "' + operation.name + '".'); - } + console.error('StateProvider error: Unsupported Tests Crud action "' + operation.name + '".'); return Promise.resolve(null); }), diff --git a/src/testApp/internal/booksInternal.ts b/src/testApp/internal/booksInternal.ts index 38759050..70de45bb 100644 --- a/src/testApp/internal/booksInternal.ts +++ b/src/testApp/internal/booksInternal.ts @@ -8,17 +8,19 @@ export type Book = { publishedAt: string; }; -const baseBooks: Array = Array(25) - .fill(undefined) - .map((_, i) => { - return { - id: faker.string.uuid(), - title: i + 1 + ' ' + faker.music.songName(), - description: faker.lorem.lines(3), - numberOfPages: faker.number.int({ min: 50, max: 800 }), - publishedAt: faker.date.anytime().toISOString() - }; - }); +function getBaseBooks(): Array { + return Array(25) + .fill(undefined) + .map((_, i) => { + return { + id: faker.string.uuid(), + title: i + 1 + ' ' + faker.music.songName(), + description: faker.lorem.lines(3), + numberOfPages: faker.number.int({ min: 50, max: 800 }), + publishedAt: faker.date.anytime().toISOString() + }; + }); +} export function getMemoryBooks(): Array { if (typeof window === 'undefined') { @@ -27,7 +29,7 @@ export function getMemoryBooks(): Array { let memory = window.localStorage.getItem('books'); if (memory === null || memory === undefined || memory === '') { - memory = JSON.stringify(baseBooks); + memory = JSON.stringify(getBaseBooks()); window.localStorage.setItem('books', memory); } diff --git a/src/testApp/internal/testsInternal.ts b/src/testApp/internal/testsInternal.ts index ef9f938a..c6d69949 100644 --- a/src/testApp/internal/testsInternal.ts +++ b/src/testApp/internal/testsInternal.ts @@ -1,4 +1,5 @@ import { faker } from '@faker-js/faker'; +import { getMemoryBooks } from './booksInternal'; export type Test = { id: string; @@ -21,25 +22,29 @@ function fakeObject(): object | string { }; } -const baseTests: Array = Array(10) - .fill(undefined) - .map((): Test => { - const date = faker.date.anytime(); - date.setHours(0, 0, 0); - date.setUTCSeconds(0, 0); - return { - id: faker.string.uuid(), - text_field: faker.music.songName(), - textarea_field: faker.lorem.lines(2), - checkbox_field: faker.datatype.boolean(), - number_field: faker.number.int({ min: -10000, max: 10000 }), - toggle_field: faker.datatype.boolean(), - url_field: faker.internet.url(), - path_field: faker.system.filePath().replace(/^\/[^/]+\//g, '/'), - date_field: date, - key_value_object_field: fakeObject() as object - }; - }); +function getBaseTests(): Array { + const books = getMemoryBooks(); + return Array(10) + .fill(undefined) + .map((): Test => { + const date = faker.date.anytime(); + date.setHours(0, 0, 0); + date.setUTCSeconds(0, 0); + return { + id: faker.string.uuid(), + text_field: faker.music.songName(), + textarea_field: faker.lorem.lines(2), + checkbox_field: faker.datatype.boolean(), + number_field: faker.number.int({ min: -10000, max: 10000 }), + toggle_field: faker.datatype.boolean(), + url_field: faker.internet.url(), + path_field: faker.system.filePath().replace(/^\/[^/]+\//g, '/'), + date_field: date, + key_value_object_field: fakeObject() as object, + crud_entity_field: books[Math.floor(Math.random() * books.length)].id ?? undefined + }; + }); +} export function getMemoryTests(): Array { //return []; @@ -50,7 +55,7 @@ export function getMemoryTests(): Array { let memory = window.localStorage.getItem('tests'); if (memory === null || memory === undefined || memory === '') { - memory = JSON.stringify(baseTests); + memory = JSON.stringify(getBaseTests()); window.localStorage.setItem('tests', memory); } diff --git a/vite.config.ts b/vite.config.ts index bbf8c7da..37dbfa81 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,6 +1,19 @@ import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vite'; +import { configDefaults } from 'vitest/config'; export default defineConfig({ - plugins: [sveltekit()] + plugins: [sveltekit()], + test: { + include: ['src/**/*.{test,spec}.ts'], + exclude: [...configDefaults.exclude, '**/build/**', '**/.svelte-kit/**', '**/dist/**'], + coverage: { + exclude: [ + ...(configDefaults?.coverage?.exclude ?? {}), + '**/build/**', + '**/.svelte-kit/**', + '**/dist/**' + ] + } + } }); diff --git a/yarn.lock b/yarn.lock index 2c771bfd..e7797215 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15,6 +15,35 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" +"@babel/helper-string-parser@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83" + integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ== + +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + +"@babel/parser@^7.23.6": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.9.tgz#7b903b6149b0f8fa7ad564af646c4c38a77fc44b" + integrity sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA== + +"@babel/types@^7.23.6": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.9.tgz#1dd7b59a9a2b5c87f8b41e52770b5ecbf492e002" + integrity sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q== + dependencies: + "@babel/helper-string-parser" "^7.23.4" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + "@esbuild/aix-ppc64@0.19.11": version "0.19.11" resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz#2acd20be6d4f0458bc8c784103495ff24f13b1d3" @@ -225,6 +254,11 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz#e5211452df060fa8522b55c7b3c0c4d1981cb044" integrity sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw== +"@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + "@jest/schemas@^29.6.3": version "29.6.3" resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" @@ -256,6 +290,14 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== +"@jridgewell/trace-mapping@^0.3.12": + version "0.3.22" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz#72a621e5de59f5f1ef792d0793a82ee20f645e4c" + integrity sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.9": version "0.3.20" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f" @@ -431,6 +473,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== +"@types/istanbul-lib-coverage@^2.0.1": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + "@types/json-schema@^7.0.12": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" @@ -542,6 +589,25 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== +"@vitest/coverage-v8@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@vitest/coverage-v8/-/coverage-v8-1.2.2.tgz#681f4f76de896d0d2484cca32285477e288fec3a" + integrity sha512-IHyKnDz18SFclIEEAHb9Y4Uxx0sPKC2VO1kdDCs1BF6Ip4S8rQprs971zIsooLUn7Afs71GRxWMWpkCGZpRMhw== + dependencies: + "@ampproject/remapping" "^2.2.1" + "@bcoe/v8-coverage" "^0.2.3" + debug "^4.3.4" + istanbul-lib-coverage "^3.2.2" + istanbul-lib-report "^3.0.1" + istanbul-lib-source-maps "^4.0.1" + istanbul-reports "^3.1.6" + magic-string "^0.30.5" + magicast "^0.3.3" + picocolors "^1.0.0" + std-env "^3.5.0" + test-exclude "^6.0.0" + v8-to-istanbul "^9.2.0" + "@vitest/expect@1.1.3": version "1.1.3" resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-1.1.3.tgz#9667719dffa82e7350dcca7b95f9ec30426d037e" @@ -832,6 +898,11 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + cookie@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" @@ -1297,7 +1368,7 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -glob@^7.1.3: +glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -1364,6 +1435,11 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + human-signals@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" @@ -1478,6 +1554,37 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-report@^3.0.0, istanbul-lib-report@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.6: + version "3.1.6" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.6.tgz#2544bcab4768154281a2f0870471902704ccaa1a" + integrity sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" @@ -1600,6 +1707,22 @@ magic-string@^0.30.4, magic-string@^0.30.5: dependencies: "@jridgewell/sourcemap-codec" "^1.4.15" +magicast@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/magicast/-/magicast-0.3.3.tgz#a15760f982deec9dabc5f314e318d7c6bddcb27b" + integrity sha512-ZbrP1Qxnpoes8sz47AM0z08U+jW6TyRgZzcWy3Ma3vDhJttwMwAFDMMQFobwdBxByBD46JYmxRzeF7w2+wJEuw== + dependencies: + "@babel/parser" "^7.23.6" + "@babel/types" "^7.23.6" + source-map-js "^1.0.2" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + mdn-data@2.0.30: version "2.0.30" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc" @@ -1666,7 +1789,7 @@ minimatch@9.0.3: dependencies: brace-expansion "^2.0.1" -minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -2136,6 +2259,11 @@ sorcery@^0.11.0: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== +source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + stackback@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" @@ -2265,6 +2393,15 @@ svelte@^4.0.0: magic-string "^0.30.4" periscopic "^3.1.0" +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -2301,6 +2438,11 @@ tinyspy@^2.2.0: resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-2.2.0.tgz#9dc04b072746520b432f77ea2c2d17933de5d6ce" integrity sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg== +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -2372,6 +2514,15 @@ util-deprecate@^1.0.2: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== +v8-to-istanbul@^9.2.0: + version "9.2.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz#2ed7644a245cddd83d4e087b9b33b3e62dfd10ad" + integrity sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + vite-node@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-1.1.3.tgz#196de20a7c2e0467a07da0dd1fe67994f5b79695"