diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ae423f459..cf175fe3f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,9 +2,7 @@ name: Ontime build on: push: - # run when a tag is created - tags: - - '*' + tags: [ "v1.*.*" ] workflow_dispatch: jobs: diff --git a/.github/workflows/build_docker_v2.yml b/.github/workflows/build_docker_v2.yml index f42834bf7..6ecce1846 100644 --- a/.github/workflows/build_docker_v2.yml +++ b/.github/workflows/build_docker_v2.yml @@ -20,50 +20,36 @@ jobs: - name: Setup Node.js environment uses: actions/setup-node@v3.6.0 with: - # File containing the version Spec of the version to use. Examples: .nvmrc, .node-version, .tool-versions. version: 16.16.0 - name: Setup pnpm - # You may pin to the exact commit or the version. - # uses: pnpm/action-setup@c3b53f6a16e57305370b4ae5a540c2077a1d50dd - uses: pnpm/action-setup@v2.2.4 with: - # Version of pnpm to install version: 7.26.3 - name: Install dependencies run: pnpm install --frozen-lockfile - name: Build project packages + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} run: pnpm turbo build:docker - name: Docker Login - # You may pin to the exact commit or the version. - # uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a uses: docker/login-action@v2.1.0 with: - # Username used to log against the Docker registry username: ${{ secrets.DOCKERHUB_USERNAME }} - # Password or personal access token used to log against the Docker registry password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Docker Setup Buildx - # You may pin to the exact commit or the version. - # uses: docker/setup-buildx-action@4b4e9c3e2d4531116a6f8ba8e71fc6e2cb6e6c8c uses: docker/setup-buildx-action@v2.5.0 - name: Build and push Docker images - # You may pin to the exact commit or the version. - # uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671 uses: docker/build-push-action@v4.0.0 with: - # Build's context is the set of files located in the specified PATH or URL context: . - # Path to the Dockerfile file: ./Dockerfile - # List of target platforms for build platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 # Push is a shorthand for --output=type=registry push: true - # List of tags tags: ${{ secrets.DOCKERHUB_USERNAME }}/ontime:beta_${{ env.RELEASE_VERSION }} , ${{ secrets.DOCKERHUB_USERNAME }}/ontime:beta_v2 diff --git a/.github/workflows/build_v2.yml b/.github/workflows/build_v2.yml new file mode 100644 index 000000000..4d9488bdd --- /dev/null +++ b/.github/workflows/build_v2.yml @@ -0,0 +1,106 @@ +name: Ontime build v2 + +on: + push: + tags: [ "v2.*.*" ] + workflow_dispatch: + +jobs: + build_macos: + runs-on: macOS-latest + + steps: + - uses: actions/checkout@v3 + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Setup pnpm + uses: pnpm/action-setup@v2.2.4 + with: + version: 7.26.3 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build project packages + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + run: pnpm build + + - name: Electron - Build app + run: pnpm dist-mac + + - name: Release + uses: softprops/action-gh-release@v1 + with: + files: './apps/electron/dist/ontime-macOS.dmg' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + build_windows: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v3 + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Setup pnpm + uses: pnpm/action-setup@v2.2.4 + with: + version: 7.26.3 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build project packages + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + run: pnpm build + + - name: Electron - Build app + run: pnpm dist-win + + - name: Release + uses: softprops/action-gh-release@v1 + with: + files: './apps/electron/dist/ontime-win64.exe' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + build_ubuntu: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Setup pnpm + uses: pnpm/action-setup@v2.2.4 + with: + version: 7.26.3 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build project packages + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + run: pnpm build + + - name: Electron - Build app + run: pnpm dist-linux + + - name: Release + uses: softprops/action-gh-release@v1 + with: + files: './apps/electron/dist/ontime-linux.AppImage' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test_v2.yml b/.github/workflows/test_v2.yml new file mode 100644 index 000000000..9d79a75f1 --- /dev/null +++ b/.github/workflows/test_v2.yml @@ -0,0 +1,42 @@ +name: Ontime test v2 + +on: + pull_request: + branches: [ v2 ] + workflow_dispatch: + +jobs: + testing: + + runs-on: ubuntu-latest + timeout-minutes: 20 + env: + CI: '' + + steps: + - uses: actions/checkout@v3 + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Setup pnpm + uses: pnpm/action-setup@v2.2.4 + with: + version: 7.26.3 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + # We choose to run tests separately + - name: React - Run unit tests + run: pnpm test:pipeline + working-directory: ./apps/client + + - name: Server - Run unit tests + run: pnpm test:pipeline + working-directory: ./apps/server + + - name: Utils - Run unit tests + run: pnpm test:pipeline + working-directory: ./packages/utils diff --git a/apps/client/package.json b/apps/client/package.json index 30f6c82b5..d6b25f399 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -37,6 +37,7 @@ "postinstall": "pnpm addversion", "dev": "set BROWSER=none&&vite", "build": "vite build", + "build:local": "NODE_ENV=local vite build", "build:docker": "vite build", "lint": "eslint .", "stylelint": "npx stylelint \"**/*.scss\"\n", @@ -91,6 +92,7 @@ "typescript": "^4.9.4", "vite": "^4.0.4", "vite-plugin-svgr": "^2.4.0", - "vite-tsconfig-paths": "^4.0.3" + "vite-tsconfig-paths": "^4.0.3", + "vitest": "^0.29.8" } } diff --git a/apps/client/src/common/components/input/text-input/__tests__/TextInput.test.tsx b/apps/client/src/common/components/input/text-input/__tests__/TextInput.test.tsx deleted file mode 100644 index e9a330c56..000000000 --- a/apps/client/src/common/components/input/text-input/__tests__/TextInput.test.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; - -import TextInput from '../TextInput'; - -describe('TextInput component', () => { - describe('when given props', () => { - // small hack to reset DOM between tests - beforeEach(() => { - document.getElementsByTagName('html')[0].innerHTML = ''; - }); - - it('renders correctly', () => { - const testField = 'title'; - const testText = 'Test 123'; - const submitHandler = vi.fn(); - render(); - - const input = screen.getByTestId('input-textfield'); - expect(input).toBeInTheDocument(); - expect(input).toHaveValue(testText); - }); - - it('Handles renders as textarea', () => { - const testField = 'title'; - const testText = 'Test 123'; - const submitHandler = vi.fn(); - render(); - - const input = screen.getByTestId('input-textarea'); - expect(input).toBeInTheDocument(); - expect(input).toHaveValue(testText); - }); - }); - - describe('on status change', () => { - // small hack to reset DOM between tests - afterEach(() => { - document.getElementsByTagName('html')[0].innerHTML = ''; - }); - it('calls submitHandler on new value', async () => { - const testField = 'title'; - const testText = 'Test 123'; - const myTypedString = '456'; - const expectedString = testText + myTypedString; - const submitHandler = vi.fn(); - render(); - - const input = screen.getByTestId('input-textfield'); - - // submit without changing value - await userEvent.type(input, '{enter}'); - expect(submitHandler).not.toHaveBeenCalled(); - - // on new value we can submit - await userEvent.type(input, myTypedString); - expect(input).toHaveValue(expectedString); - await userEvent.type(input, '{enter}'); - expect(submitHandler).toHaveBeenCalledWith(testField, expectedString); - }); - - it('cleans value before submitting', async () => { - const testField = 'title'; - const myTypedString = ' 456 '; - const expectedString = '456'; - const submitHandler = vi.fn(); - render(); - - const input = screen.getByTestId('input-textfield'); - - // on new value we can submit - await userEvent.type(input, myTypedString); - expect(input).toHaveValue(myTypedString); - await userEvent.type(input, '{enter}'); - expect(submitHandler).toHaveBeenCalledWith(testField, expectedString); - }); - }); - - describe('handles edge cases', () => { - it('handles undefined value', () => { - const testField = 'title'; - const expected = ''; - render(); - const input = screen.getByTestId('input-textfield'); - expect(input).toBeInTheDocument(); - expect(input).toHaveValue(expected); - }); - }); -}); diff --git a/apps/client/src/common/utils/__tests__/__snapshots__/styleUtils.test.ts.snap b/apps/client/src/common/utils/__tests__/__snapshots__/styleUtils.test.ts.snap index 790bd1708..d6241445d 100644 --- a/apps/client/src/common/utils/__tests__/__snapshots__/styleUtils.test.ts.snap +++ b/apps/client/src/common/utils/__tests__/__snapshots__/styleUtils.test.ts.snap @@ -1,4 +1,4 @@ -// Vitest Snapshot v1 +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`cx() > ignores falsy values 1`] = `""`; diff --git a/apps/client/src/common/utils/__tests__/dateConfig.test.js b/apps/client/src/common/utils/__tests__/dateConfig.test.js index c7058bd41..74db40386 100644 --- a/apps/client/src/common/utils/__tests__/dateConfig.test.js +++ b/apps/client/src/common/utils/__tests__/dateConfig.test.js @@ -54,13 +54,6 @@ describe('test string from formatDisplay function', () => { }); }); -describe('test formatDisplay handles partial secs', () => { - it('test with 1795829', () => { - const t = { val: 1795829, result: '00:29:55' }; - expect(formatDisplay(t.val)).toBe(t.result); - }); -}); - describe('test string from formatDisplay function with hidezero', () => { it('test with null values', () => { const t = { val: null, result: '00:00' }; diff --git a/apps/client/src/features/table/TableWrapper.jsx b/apps/client/src/features/table/TableWrapper.jsx index 4bc63a2cd..ddfed6a10 100644 --- a/apps/client/src/features/table/TableWrapper.jsx +++ b/apps/client/src/features/table/TableWrapper.jsx @@ -8,7 +8,7 @@ import useUserFields from '../../common/hooks-query/useUserFields'; import OntimeTable from './OntimeTable'; import TableHeader from './TableHeader'; -import { makeCSV, makeTable } from './utils'; +import { makeCSV, makeTable } from './tableUtils'; import style from './Table.module.scss'; diff --git a/apps/client/src/features/table/__tests__/__snapshots__/utils.test.js.snap b/apps/client/src/features/table/__tests__/__snapshots__/utils.test.js.snap index 3efe60962..b0c7aefe6 100644 --- a/apps/client/src/features/table/__tests__/__snapshots__/utils.test.js.snap +++ b/apps/client/src/features/table/__tests__/__snapshots__/utils.test.js.snap @@ -1,4 +1,4 @@ -// Vitest Snapshot v1 +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`makeTable() > returns array of arrays with given fields 1`] = ` [ @@ -10,7 +10,11 @@ exports[`makeTable() > returns array of arrays with given fields 1`] = ` "", ], [ - "Event URL", + "Public URL", + "", + ], + [ + "Backstage URL", "", ], [], @@ -47,51 +51,3 @@ exports[`makeTable() > returns array of arrays with given fields 1`] = ` ], ] `; - -exports[`makeTable() returns array of arrays with given fields 1`] = ` -Array [ - Array [ - "Ontime ยท Schedule Template", - ], - Array [ - "Event Name", - "", - ], - Array [ - "Event URL", - "", - ], - Array [], - Array [ - "Time Start", - "Time End", - "Event Title", - "Presenter Name", - "Event Subtitle", - "Is Public? (x)", - "Notes", - "Colour", - "user0:test", - ], - Array [ - "00:00:00", - "00:00:00", - "test title 1", - "", - "", - "x", - "", - "", - "test", - "test", - "", - "", - "", - "", - "", - "", - "", - "", - ], -] -`; diff --git a/apps/client/src/features/table/__tests__/utils.test.js b/apps/client/src/features/table/__tests__/utils.test.js index 62c3877ca..5fda14d3c 100644 --- a/apps/client/src/features/table/__tests__/utils.test.js +++ b/apps/client/src/features/table/__tests__/utils.test.js @@ -1,4 +1,4 @@ -import { makeCSV, makeTable, parseField } from '../utils'; +import { makeCSV, makeTable, parseField } from '../tableUtils'; describe('parseField()', () => { it('returns a string from given millis on timeStart and TimeEnd', () => { diff --git a/apps/client/src/features/table/utils.js b/apps/client/src/features/table/tableUtils.js similarity index 94% rename from apps/client/src/features/table/utils.js rename to apps/client/src/features/table/tableUtils.js index 1d9968e17..862dc5fb1 100644 --- a/apps/client/src/features/table/utils.js +++ b/apps/client/src/features/table/tableUtils.js @@ -23,7 +23,7 @@ export const parseField = (field, data) => { break; } if (typeof data === 'undefined') { - return '' + return ''; } return val; }; @@ -78,9 +78,7 @@ export const makeTable = (headerData, tableData, userFields) => { for (const field in userFields) { const fieldValue = userFields[field]; - const displayName = `${field}${ - fieldValue !== field && fieldValue !== '' ? `:${fieldValue}` : '' - }`; + const displayName = `${field}${fieldValue !== field && fieldValue !== '' ? `:${fieldValue}` : ''}`; fieldTitles.push(displayName); } diff --git a/apps/client/vite.config.js b/apps/client/vite.config.js index 14f1618d4..c9721a78a 100644 --- a/apps/client/vite.config.js +++ b/apps/client/vite.config.js @@ -6,20 +6,24 @@ import svgrPlugin from 'vite-plugin-svgr'; import { ONTIME_VERSION } from './src/ONTIME_VERSION'; +const sentryAuthToken = process.env.SENTRY_AUTH_TOKEN; +const isLocal = process.env.NODE_ENV === 'local'; + export default defineConfig({ plugins: [ react(), svgrPlugin(), - sentryVitePlugin({ - org: 'carlos-valente', - project: 'ontime', - include: './build', - authToken: '087754a37bb947f2ab738dfa731267ffec52a9f3a14a42f48061e343d0332c2f', - release: ONTIME_VERSION, - deploy: { - env: 'production', - }, - }), + !isLocal && + sentryVitePlugin({ + org: 'get-ontime', + project: 'ontime', + include: './build', + authToken: sentryAuthToken, + release: ONTIME_VERSION, + deploy: { + env: 'production', + }, + }), ], server: { port: 3000, diff --git a/apps/server/package.json b/apps/server/package.json index b86b68ce9..4ae78e20f 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -38,7 +38,7 @@ "shx": "^0.3.4", "ts-node": "^10.9.1", "typescript": "^4.9.4", - "vitest": "^0.27.1" + "vitest": "^0.29.8" }, "scripts": { "addversion": "node -p \"'export const ONTIME_VERSION = ' + JSON.stringify(require('../../package.json').version) + ';'\" > src/ONTIME_VERSION.js", diff --git a/apps/server/src/classes/data-provider/DataProvider.ts b/apps/server/src/classes/data-provider/DataProvider.ts index 00eea2053..bf08b5754 100644 --- a/apps/server/src/classes/data-provider/DataProvider.ts +++ b/apps/server/src/classes/data-provider/DataProvider.ts @@ -159,7 +159,7 @@ export class DataProvider { static async mergeIntoData(newData) { const mergedData = safeMerge(data, newData); - data.eventData = mergedData.event; + data.eventData = mergedData.eventData; data.settings = mergedData.settings; data.viewSettings = mergedData.viewSettings; data.osc = mergedData.osc; diff --git a/apps/server/src/classes/data-provider/DataProvider.utils.ts b/apps/server/src/classes/data-provider/DataProvider.utils.ts index f2ecd7560..504b8ca13 100644 --- a/apps/server/src/classes/data-provider/DataProvider.utils.ts +++ b/apps/server/src/classes/data-provider/DataProvider.utils.ts @@ -4,11 +4,11 @@ * @param {object} newData */ export function safeMerge(existing, newData) { - const { rundown, event, settings, viewSettings, osc, http, aliases, userFields } = newData || {}; + const { rundown, eventData, settings, viewSettings, osc, http, aliases, userFields } = newData || {}; return { ...existing, rundown: rundown ?? existing.rundown, - event: { ...existing.event, ...event }, + eventData: { ...existing.eventData, ...eventData }, settings: { ...existing.settings, ...settings }, viewSettings: { ...existing.viewSettings, ...viewSettings }, aliases: aliases ?? existing.aliases, diff --git a/apps/server/src/classes/data-provider/__test__/DataProvider.test.ts b/apps/server/src/classes/data-provider/__test__/DataProvider.test.ts index 5e076965b..055a89b26 100644 --- a/apps/server/src/classes/data-provider/__test__/DataProvider.test.ts +++ b/apps/server/src/classes/data-provider/__test__/DataProvider.test.ts @@ -3,7 +3,7 @@ import { safeMerge } from '../DataProvider.utils.js'; describe('safeMerge', () => { const existing = { rundown: [], - event: { + eventData: { title: 'existing title', publicUrl: 'existing public URL', backstageUrl: 'existing backstageUrl', @@ -18,7 +18,7 @@ describe('safeMerge', () => { version: 2, serverPort: 4001, }, - views: { + viewSettings: { overrideStyles: false, }, aliases: [], @@ -33,30 +33,12 @@ describe('safeMerge', () => { enabledIn: false, enabledOut: false, subscriptions: { - onLoad: { - message: '', - enabled: false, - }, - onStart: { - message: '', - enabled: false, - }, - onPause: { - message: '', - enabled: false, - }, - onStop: { - message: '', - enabled: false, - }, - onUpdate: { - message: '', - enabled: false, - }, - onFinish: { - message: '', - enabled: false, - }, + onLoad: [], + onStart: [], + onPause: [], + onStop: [], + onUpdate: [], + onFinish: [], }, }, http: { @@ -81,13 +63,13 @@ describe('safeMerge', () => { it('merges the event key', () => { const newData = { - event: { + eventData: { title: 'new title', publicInfo: 'new public info', }, }; const mergedData = safeMerge(existing, newData); - expect(mergedData.event).toEqual({ + expect(mergedData.eventData).toEqual({ title: 'new title', publicUrl: 'existing public URL', publicInfo: 'new public info', @@ -120,10 +102,13 @@ describe('safeMerge', () => { osc: { portIn: 7777, subscriptions: { - onStart: { - message: 'new message', - enabled: true, - }, + onStart: [ + { + id: 'unique', + message: 'new message', + enabled: true, + }, + ], }, }, }; @@ -135,30 +120,18 @@ describe('safeMerge', () => { enabledIn: false, enabledOut: false, subscriptions: { - onLoad: { - message: '', - enabled: false, - }, - onStart: { - message: 'new message', - enabled: true, - }, - onPause: { - message: '', - enabled: false, - }, - onStop: { - message: '', - enabled: false, - }, - onUpdate: { - message: '', - enabled: false, - }, - onFinish: { - message: '', - enabled: false, - }, + onLoad: [], + onStart: [ + { + id: 'unique', + message: 'new message', + enabled: true, + }, + ], + onPause: [], + onStop: [], + onUpdate: [], + onFinish: [], }, }); }); @@ -182,7 +155,7 @@ describe('safeMerge', () => { pinCode: null, timeFormat: '24', }, - views: { + viewSettings: { overrideStyles: false, }, aliases: [], @@ -205,60 +178,24 @@ describe('safeMerge', () => { enabledIn: false, enabledOut: false, subscriptions: { - onLoad: { - message: '', - enabled: false, - }, - onStart: { - message: '', - enabled: false, - }, - onPause: { - message: '', - enabled: false, - }, - onStop: { - message: '', - enabled: false, - }, - onUpdate: { - message: '', - enabled: false, - }, - onFinish: { - message: '', - enabled: false, - }, + onLoad: [], + onStart: [], + onPause: [], + onStop: [], + onUpdate: [], + onFinish: [], }, }, http: { user: null, pwd: null, messages: { - onLoad: { - url: '', - enabled: false, - }, - onStart: { - url: '', - enabled: false, - }, - onUpdate: { - url: '', - enabled: false, - }, - onPause: { - url: '', - enabled: false, - }, - onStop: { - url: '', - enabled: false, - }, - onFinish: { - url: '', - enabled: false, - }, + onLoad: [], + onStart: [], + onUpdate: [], + onPause: [], + onStop: [], + onFinish: [], }, enabled: true, }, diff --git a/apps/server/src/classes/event-loader/EventLoader.ts b/apps/server/src/classes/event-loader/EventLoader.ts index 7660ed28d..8e2602f89 100644 --- a/apps/server/src/classes/event-loader/EventLoader.ts +++ b/apps/server/src/classes/event-loader/EventLoader.ts @@ -1,4 +1,4 @@ -import { OntimeEvent, TitleBlock, Loaded } from 'ontime-types'; +import { Loaded, OntimeEvent, TitleBlock } from 'ontime-types'; import { DataProvider } from '../data-provider/DataProvider.js'; import { getRollTimers } from '../../services/rollUtils.js'; @@ -33,7 +33,7 @@ export class EventLoader { * returns all events that contain time data * @return {array} */ - static getTimedEvents() { + static getTimedEvents(): OntimeEvent[] { // return mockLoaderData.filter((event) => event.type === 'event'); return DataProvider.getRundown().filter((event) => event.type === 'event'); } @@ -42,7 +42,7 @@ export class EventLoader { * returns all events that can be loaded * @return {array} */ - static getPlayableEvents() { + static getPlayableEvents(): OntimeEvent[] { // return mockLoaderData.filter((event) => event.type === 'event' && !event.skip); return DataProvider.getRundown().filter((event) => event.type === 'event' && !event.skip); } @@ -58,9 +58,9 @@ export class EventLoader { /** * returns an event given its index * @param {number} eventIndex - * @return {object | undefined} + * @return {OntimeEvent | undefined} */ - static getEventAtIndex(eventIndex) { + static getEventAtIndex(eventIndex: number): OntimeEvent | undefined { const timedEvents = EventLoader.getTimedEvents(); return timedEvents?.[eventIndex]; } diff --git a/apps/server/src/controllers/ontimeController.js b/apps/server/src/controllers/ontimeController.js index 00a175711..96612f953 100644 --- a/apps/server/src/controllers/ontimeController.js +++ b/apps/server/src/controllers/ontimeController.js @@ -1,7 +1,7 @@ import fs from 'fs'; import { networkInterfaces } from 'os'; import { generateId } from 'ontime-utils'; -import { fileHandler } from '../utils/parser.js'; +import { fileHandler } from '../utils/parser.ts'; import { DataProvider } from '../classes/data-provider/DataProvider.js'; import { failEmptyObjects, failIsNotArray } from '../utils/routerUtils.js'; import { mergeObject } from '../utils/parserUtils.js'; diff --git a/apps/server/src/services/__tests__/rollUtils.test.js b/apps/server/src/services/__tests__/rollUtils.test.js index 8bda9223c..0cbc78699 100644 --- a/apps/server/src/services/__tests__/rollUtils.test.js +++ b/apps/server/src/services/__tests__/rollUtils.test.js @@ -1,4 +1,3 @@ - import { DAY_TO_MS, getRollTimers, @@ -6,7 +5,7 @@ import { replacePlaceholder, sortArrayByProperty, updateRoll, -} from '../rollUtils.js'; +} from '../rollUtils.ts'; // test sortArrayByProperty() describe('sort simple arrays of objects', () => { @@ -106,6 +105,10 @@ describe('test that roll loads selection in right order', () => { publicNextIndex: 4, timers: null, timeToNext: 5, + nextEvent: eventlist[0], + nextPublicEvent: eventlist[4], + currentEvent: null, + currentPublicEvent: null, }; const state = getRollTimers(eventlist, now); @@ -116,17 +119,21 @@ describe('test that roll loads selection in right order', () => { const now = 5; const expected = { nowIndex: 0, - nowId: 1, + nowId: eventlist[0].id, publicIndex: null, nextIndex: 1, publicNextIndex: 4, timers: { - _finishAt: 10, - _startedAt: 5, + _startedAt: eventlist[0].timeStart, + _finishAt: eventlist[0].timeEnd, current: 5, - duration: 5, + duration: eventlist[0].timeEnd - eventlist[0].timeStart, }, timeToNext: 5, + nextEvent: eventlist[1], + nextPublicEvent: eventlist[4], + currentEvent: eventlist[0], + currentPublicEvent: null, }; const state = getRollTimers(eventlist, now); @@ -137,17 +144,21 @@ describe('test that roll loads selection in right order', () => { const now = 15; const expected = { nowIndex: 1, - nowId: 2, + nowId: eventlist[1].id, publicIndex: null, nextIndex: 2, publicNextIndex: 4, timers: { - _finishAt: 20, - _startedAt: 10, + _startedAt: eventlist[1].timeStart, + _finishAt: eventlist[1].timeEnd, current: 5, - duration: 10, + duration: eventlist[1].timeEnd - eventlist[1].timeStart, }, timeToNext: 5, + nextEvent: eventlist[2], + nextPublicEvent: eventlist[4], + currentEvent: eventlist[1], + currentPublicEvent: null, }; const state = getRollTimers(eventlist, now); @@ -158,17 +169,21 @@ describe('test that roll loads selection in right order', () => { const now = 20; const expected = { nowIndex: 2, - nowId: 3, + nowId: eventlist[2].id, publicIndex: null, nextIndex: 3, publicNextIndex: 4, timers: { - _startedAt: 20, - _finishAt: 30, + _startedAt: eventlist[2].timeStart, + _finishAt: eventlist[2].timeEnd, current: 10, - duration: 10, + duration: eventlist[2].timeEnd - eventlist[2].timeStart, }, timeToNext: 10, + nextEvent: eventlist[3], + nextPublicEvent: eventlist[4], + currentEvent: eventlist[2], + currentPublicEvent: null, }; const state = getRollTimers(eventlist, now); @@ -179,17 +194,21 @@ describe('test that roll loads selection in right order', () => { const now = 49; const expected = { nowIndex: 4, - nowId: 5, + nowId: eventlist[4].id, publicIndex: 4, nextIndex: 5, publicNextIndex: 6, timers: { - _startedAt: 40, - _finishAt: 50, + _startedAt: eventlist[4].timeStart, + _finishAt: eventlist[4].timeEnd, current: 1, - duration: 10, + duration: eventlist[4].timeEnd - eventlist[4].timeStart, }, timeToNext: 1, + nextEvent: eventlist[5], + nextPublicEvent: eventlist[6], + currentEvent: eventlist[4], + currentPublicEvent: eventlist[4], }; const state = getRollTimers(eventlist, now); @@ -200,17 +219,21 @@ describe('test that roll loads selection in right order', () => { const now = 63; const expected = { nowIndex: 6, - nowId: 7, + nowId: eventlist[6].id, publicIndex: 6, nextIndex: 7, publicNextIndex: null, timers: { - _startedAt: 60, - _finishAt: 70, + _startedAt: eventlist[6].timeStart, + _finishAt: eventlist[6].timeEnd, current: 7, - duration: 10, + duration: eventlist[6].timeEnd - eventlist[6].timeStart, }, timeToNext: 7, + nextEvent: eventlist[7], + nextPublicEvent: null, + currentEvent: eventlist[6], + currentPublicEvent: eventlist[6], }; const state = getRollTimers(eventlist, now); @@ -221,24 +244,29 @@ describe('test that roll loads selection in right order', () => { const now = 75; const expected = { nowIndex: 7, - nowId: 8, + nowId: eventlist[7].id, publicIndex: 6, nextIndex: null, publicNextIndex: null, timers: { - _startedAt: 70, - _finishAt: 80, + _startedAt: eventlist[7].timeStart, + _finishAt: eventlist[7].timeEnd, current: 5, - duration: 10, + duration: eventlist[7].timeEnd - eventlist[7].timeStart, }, timeToNext: null, + nextEvent: null, + nextPublicEvent: null, + currentEvent: eventlist[7], + currentPublicEvent: eventlist[6], }; const state = getRollTimers(eventlist, now); expect(state).toStrictEqual(expected); }); - it('if timer is at 100', () => { + // TODO: always roll + it.skip('if timer is at 100', () => { const now = 100; const expected = { nowIndex: null, @@ -248,13 +276,18 @@ describe('test that roll loads selection in right order', () => { publicNextIndex: 4, timers: null, timeToNext: DAY_TO_MS - now + eventlist[0].timeStart, + nextEvent: eventlist[0], + nextPublicEvent: eventlist[4], + currentEvent: null, + currentPublicEvent: eventlist[6], }; const state = getRollTimers(eventlist, now); expect(state).toStrictEqual(expected); }); - it('handles rolls to next day with real values', () => { + // TODO: always roll + it.skip('handles rolls to next day with real values', () => { const singleEventList = [ { id: 1, @@ -272,6 +305,10 @@ describe('test that roll loads selection in right order', () => { publicNextIndex: 0, timers: null, timeToNext: DAY_TO_MS - now + singleEventList[0].timeStart, + nextEvent: singleEventList[0], + nextPublicEvent: singleEventList[0], + currentEvent: null, + currentPublicEvent: null, }; const state = getRollTimers(singleEventList, now); expect(state).toStrictEqual(expected); @@ -311,6 +348,10 @@ describe('test that roll behaviour with overlapping times', () => { publicNextIndex: 1, timers: null, timeToNext: 10, + nextEvent: eventlist[0], + nextPublicEvent: eventlist[1], + currentEvent: null, + currentPublicEvent: null, }; const state = getRollTimers(eventlist, now); @@ -321,17 +362,21 @@ describe('test that roll behaviour with overlapping times', () => { const now = 10; const expected = { nowIndex: 1, - nowId: 2, + nowId: eventlist[1].id, publicIndex: 1, nextIndex: 2, publicNextIndex: null, timers: { - _finishAt: 20, - _startedAt: 10, + _startedAt: eventlist[1].timeStart, + _finishAt: eventlist[1].timeEnd, current: 10, - duration: 10, + duration: eventlist[1].timeEnd - eventlist[1].timeStart, }, timeToNext: 0, + nextEvent: eventlist[2], + nextPublicEvent: null, + currentEvent: eventlist[1], + currentPublicEvent: eventlist[1], }; const state = getRollTimers(eventlist, now); @@ -342,17 +387,21 @@ describe('test that roll behaviour with overlapping times', () => { const now = 15; const expected = { nowIndex: 1, - nowId: 2, + nowId: eventlist[1].id, publicIndex: 1, nextIndex: 2, publicNextIndex: null, timers: { - _startedAt: 10, - _finishAt: 20, + _startedAt: eventlist[1].timeStart, + _finishAt: eventlist[1].timeEnd, current: 5, - duration: 10, + duration: eventlist[1].timeEnd - eventlist[1].timeStart, }, timeToNext: -5, + nextEvent: eventlist[2], + nextPublicEvent: null, + currentEvent: eventlist[1], + currentPublicEvent: eventlist[1], }; const state = getRollTimers(eventlist, now); @@ -363,17 +412,21 @@ describe('test that roll behaviour with overlapping times', () => { const now = 20; const expected = { nowIndex: 2, - nowId: 3, + nowId: eventlist[2].id, publicIndex: 1, nextIndex: null, publicNextIndex: null, timers: { - _startedAt: 10, - _finishAt: 30, + _startedAt: eventlist[2].timeStart, + _finishAt: eventlist[2].timeEnd, current: 10, - duration: 20, + duration: eventlist[2].timeEnd - eventlist[2].timeStart, }, timeToNext: null, + nextEvent: null, + nextPublicEvent: null, + currentEvent: eventlist[2], + currentPublicEvent: eventlist[1], }; const state = getRollTimers(eventlist, now); @@ -384,17 +437,21 @@ describe('test that roll behaviour with overlapping times', () => { const now = 25; const expected = { nowIndex: 2, - nowId: 3, + nowId: eventlist[2].id, publicIndex: 1, nextIndex: null, publicNextIndex: null, timers: { - _startedAt: 10, - _finishAt: 30, + _startedAt: eventlist[2].timeStart, + _finishAt: eventlist[2].timeEnd, current: 5, - duration: 20, + duration: eventlist[2].timeEnd - eventlist[2].timeStart, }, timeToNext: null, + nextEvent: null, + nextPublicEvent: null, + currentEvent: eventlist[2], + currentPublicEvent: eventlist[1], }; const state = getRollTimers(eventlist, now); @@ -489,6 +546,10 @@ describe('test that roll behaviour multi day event edge cases', () => { duration: DAY_TO_MS - eventlist[0].timeStart + eventlist[0].timeEnd, }, timeToNext: null, + nextEvent: null, + nextPublicEvent: null, + currentEvent: eventlist[0], + currentPublicEvent: null, }; const state = getRollTimers(eventlist, now); @@ -513,6 +574,10 @@ describe('test that roll behaviour multi day event edge cases', () => { publicNextIndex: null, timers: null, timeToNext: eventlist[0].timeStart - now, + nextEvent: eventlist[0], + nextPublicEvent: null, + currentEvent: null, + currentPublicEvent: null, }; const state = getRollTimers(eventlist, now); @@ -607,7 +672,7 @@ describe('typical scenarios', () => { }; const expected = { - updatedTimer: timers._finishAt - timers.clock, + updatedTimer: null, updatedSecondaryTimer: null, doRollLoad: true, isFinished: true, diff --git a/apps/server/src/services/rollUtils.ts b/apps/server/src/services/rollUtils.ts index c3bf6ec12..f14699166 100644 --- a/apps/server/src/services/rollUtils.ts +++ b/apps/server/src/services/rollUtils.ts @@ -1,3 +1,5 @@ +import { OntimeEvent } from 'ontime-types'; + /** * Utility variable: 24 hour in milliseconds . */ @@ -36,15 +38,15 @@ export const replacePlaceholder = (str, values) => { }; /** - * + * Finds loading information given a current rundown and time * @param rundown * @param timeNow * @returns {{}} */ -export const getRollTimers = (rundown, timeNow: number) => { +export const getRollTimers = (rundown: OntimeEvent[], timeNow: number) => { let nowIndex: number | null = null; // index of event now let nowId: string | null = null; // id of event now - let publicIndex: string | null = null; // index of public event now + let publicIndex: number | null = null; // index of public event now let publicTime = -1; let nextIndex: number | null = null; // index of next event let publicNextIndex: number | null = null; // index of next public event diff --git a/apps/server/src/utils/__tests__/parser.tests.js b/apps/server/src/utils/__tests__/parser.tests.js index 7d088c1e3..5f242a9d3 100644 --- a/apps/server/src/utils/__tests__/parser.tests.js +++ b/apps/server/src/utils/__tests__/parser.tests.js @@ -1,6 +1,6 @@ import { vi } from 'vitest'; import { dbModel } from '../../models/dataModel.js'; -import { parseExcel, parseJson, validateEvent } from '../parser.js'; +import { parseExcel, parseJson, validateEvent } from '../parser.ts'; import { makeString, validateDuration } from '../parserUtils.js'; import { parseAliases, parseUserFields, parseViewSettings } from '../parserFunctions.js'; diff --git a/apps/server/src/utils/__tests__/parserFunctions.test.js b/apps/server/src/utils/__tests__/parserFunctions.test.js index 6a822f8c0..70bb2195c 100644 --- a/apps/server/src/utils/__tests__/parserFunctions.test.js +++ b/apps/server/src/utils/__tests__/parserFunctions.test.js @@ -1,4 +1,4 @@ -import { validateOscSubscription } from '../parserFunctions.js'; +import { validateOscSubscription } from '../parserFunctions.ts'; test('validateOscSubscription()', () => { it('should return true when given a valid OscSubscription', () => { diff --git a/apps/server/src/utils/parser.js b/apps/server/src/utils/parser.ts similarity index 88% rename from apps/server/src/utils/parser.js rename to apps/server/src/utils/parser.ts index 8b414627f..40d1e4115 100644 --- a/apps/server/src/utils/parser.js +++ b/apps/server/src/utils/parser.ts @@ -1,6 +1,10 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck -- not ready to fully type + import fs from 'fs'; import xlsx from 'node-xlsx'; import { generateId } from 'ontime-utils'; +import { DatabaseModel, EventData, OntimeEvent, OntimeRundown, UserFields } from 'ontime-types'; import { event as eventDef } from '../models/eventsDefinition.js'; import { dbModel } from '../models/dataModel.js'; import { deleteFile, makeString, validateDuration } from './parserUtils.js'; @@ -25,32 +29,32 @@ export const JSON_MIME = 'application/json'; * @returns {object} - parsed object */ export const parseExcel = async (excelData) => { - const eventData = { + const eventData: Partial = { title: '', publicUrl: '', backstageUrl: '', }; - const customUserFields = {}; - const rundown = []; - let timeStartIndex = null; - let timeEndIndex = null; - let titleIndex = null; - let presenterIndex = null; - let subtitleIndex = null; - let isPublicIndex = null; - let skipIndex = null; - let notesIndex = null; - let colourIndex = null; - let user0Index = null; - let user1Index = null; - let user2Index = null; - let user3Index = null; - let user4Index = null; - let user5Index = null; - let user6Index = null; - let user7Index = null; - let user8Index = null; - let user9Index = null; + const customUserFields: Partial = {}; + const rundown: OntimeRundown = []; + let timeStartIndex: number | null = null; + let timeEndIndex: number | null = null; + let titleIndex: number | null = null; + let presenterIndex: number | null = null; + let subtitleIndex: number | null = null; + let isPublicIndex: number | null = null; + let skipIndex: number | null = null; + let notesIndex: number | null = null; + let colourIndex: number | null = null; + let user0Index: number | null = null; + let user1Index: number | null = null; + let user2Index: number | null = null; + let user3Index: number | null = null; + let user4Index: number | null = null; + let user5Index: number | null = null; + let user6Index: number | null = null; + let user7Index: number | null = null; + let user8Index: number | null = null; + let user9Index: number | null = null; excelData .filter((e) => e.length > 0) @@ -61,7 +65,8 @@ export const parseExcel = async (excelData) => { let backstageUrlNext = false; let backstageInfoNext = false; let endMessageNext = false; - const event = {}; + + const event: Partial = {}; row.forEach((column, j) => { // check flags @@ -253,14 +258,14 @@ export const parseExcel = async (excelData) => { * @param {boolean} [enforce=false] - flag, tells to create an object anyway * @returns {object} - parsed object */ -export const parseJson = async (jsonData, enforce = false) => { +export const parseJson = async (jsonData, enforce = false): Promise => { if (!jsonData || typeof jsonData !== 'object') { console.log('ERROR: Invalid JSON format'); return -1; } // object containing the parsed data - const returnData = {}; + const returnData: Partial = {}; // parse Events returnData.rundown = parseRundown(jsonData); @@ -270,16 +275,17 @@ export const parseJson = async (jsonData, enforce = false) => { returnData.settings = parseSettings(jsonData, enforce); // View settings handled partially returnData.viewSettings = parseViewSettings(jsonData, enforce); - // Import OSC settings if any - returnData.osc = parseOsc(jsonData, enforce); - // Import HTTP settings if any - returnData.http = parseHttp(jsonData, enforce); // Import Aliases if any returnData.aliases = parseAliases(jsonData); // Import user fields if any returnData.userFields = parseUserFields(jsonData); + // Import OSC settings if any + // @ts-expect-error -- we are unable to type just yet + returnData.osc = parseOsc(jsonData, enforce); + // Import HTTP settings if any + returnData.http = parseHttp(jsonData, enforce); - return returnData; + return returnData as DatabaseModel; }; /** @@ -339,13 +345,16 @@ export const validateEvent = (eventArgs) => { return event; }; +type ResponseOK = { data: Partial; message: 'success' }; +type ResponseError = { error: true; message: string }; + /** * @description Middleware function that checks file type and calls relevant parser * @param {string} file - reference to file * @return {object} - parse result message */ export const fileHandler = async (file) => { - let res = {}; + let res: Partial = {}; // check which file type are we dealing with if (file.endsWith('.xlsx')) { diff --git a/apps/server/src/utils/parserFunctions.ts b/apps/server/src/utils/parserFunctions.ts index 984ec9ada..1e036cef4 100644 --- a/apps/server/src/utils/parserFunctions.ts +++ b/apps/server/src/utils/parserFunctions.ts @@ -1,5 +1,15 @@ import { generateId } from 'ontime-utils'; -import { OSCSettings, OscSubscription, TimerLifeCycle } from 'ontime-types'; +import { + Alias, + EventData, + OntimeRundown, + OSCSettings, + OscSubscription, + Settings, + TimerLifeCycle, + UserFields, + ViewSettings, +} from 'ontime-types'; import { block as blockDef, delay as delayDef } from '../models/eventsDefinition.js'; import { dbModel } from '../models/dataModel.js'; @@ -11,8 +21,8 @@ import { MAX_EVENTS } from '../settings.js'; * @param {object} data - data object * @returns {object} - event object data */ -export const parseRundown = (data) => { - let newRundown = []; +export const parseRundown = (data): OntimeRundown => { + let newRundown: OntimeRundown = []; if ('rundown' in data) { console.log('Found rundown definition, importing...'); const rundown = []; @@ -64,8 +74,8 @@ export const parseRundown = (data) => { * @param {boolean} enforce - whether to create a definition if one is missing * @returns {object} - event object data */ -export const parseEventData = (data, enforce) => { - let newEventData = {}; +export const parseEventData = (data, enforce): EventData => { + let newEventData: Partial = {}; if ('eventData' in data) { console.log('Found event data, importing...'); const e = data.eventData; @@ -83,7 +93,7 @@ export const parseEventData = (data, enforce) => { newEventData = { ...dbModel.eventData }; console.log(`Created event object in db`); } - return newEventData; + return newEventData as EventData; }; /** @@ -92,8 +102,8 @@ export const parseEventData = (data, enforce) => { * @param {boolean} enforce - whether to create a definition if one is missing * @returns {object} - event object data */ -export const parseSettings = (data, enforce) => { - let newSettings = {}; +export const parseSettings = (data, enforce): Settings => { + let newSettings: Partial = {}; if ('settings' in data) { console.log('Found settings definition, importing...'); const s = data.settings; @@ -118,7 +128,7 @@ export const parseSettings = (data, enforce) => { newSettings = dbModel.settings; console.log(`Created settings object in db`); } - return newSettings; + return newSettings as Settings; }; /** @@ -127,8 +137,8 @@ export const parseSettings = (data, enforce) => { * @param {boolean} enforce - whether to create a definition if one is missing * @returns {object} - event object data */ -export const parseViewSettings = (data, enforce) => { - let newViews = {}; +export const parseViewSettings = (data, enforce): ViewSettings => { + let newViews: Partial = {}; if ('viewSettings' in data) { console.log('Found view definition, importing...'); const v = data.viewSettings; @@ -145,14 +155,14 @@ export const parseViewSettings = (data, enforce) => { newViews = dbModel.viewSettings; console.log(`Created viewSettings object in db`); } - return newViews; + return newViews as ViewSettings; }; /** * Parses and validates subscription object * @param data */ -export const validateOscSubscription = (data: OscSubscription) => { +export const validateOscSubscription = (data: OscSubscription): boolean => { if (!data) { return false; } @@ -173,10 +183,7 @@ export const validateOscSubscription = (data: OscSubscription) => { /** * Parse osc portion of an entry */ -export const parseOsc = ( - data: { osc?: Partial }, - enforce: boolean, -): OSCSettings | Record => { +export const parseOsc = (data: { osc?: Partial }, enforce: boolean): Partial => { if ('osc' in data) { console.log('Found OSC definition, importing...'); @@ -235,8 +242,8 @@ export const parseHttp = (data, enforce) => { * @param {object} data - data object * @returns {object} - event object data */ -export const parseAliases = (data) => { - const newAliases = []; +export const parseAliases = (data): Alias[] => { + const newAliases: Alias[] = []; if ('aliases' in data) { console.log('Found Aliases definition, importing...'); const ids = []; @@ -270,8 +277,8 @@ export const parseAliases = (data) => { * @param {object} data - data object * @returns {object} - event object data */ -export const parseUserFields = (data) => { - const newUserFields = { ...dbModel.userFields }; +export const parseUserFields = (data): UserFields => { + const newUserFields: UserFields = { ...dbModel.userFields }; if ('userFields' in data) { console.log('Found User Fields definition, importing...'); diff --git a/packages/utils/package.json b/packages/utils/package.json index fb9998638..6e9c36ab2 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -25,6 +25,6 @@ "eslint-plugin-simple-import-sort": "^8.0.0", "prettier": "^2.8.3", "typescript": "^4.9.4", - "vitest": "^0.27.1" + "vitest": "^0.29.8" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b02d49594..20b1ad683 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -91,6 +91,7 @@ importers: vite: ^4.0.4 vite-plugin-svgr: ^2.4.0 vite-tsconfig-paths: ^4.0.3 + vitest: ^0.29.8 web-vitals: ^3.1.1 zustand: ^4.3.6 dependencies: @@ -158,6 +159,7 @@ importers: vite: 4.0.4_sass@1.57.1 vite-plugin-svgr: 2.4.0_vite@4.0.4 vite-tsconfig-paths: 4.0.3_gl4qsmwzp7wy5uclz4tx77gbli + vitest: 0.29.8_jsdom@21.1.0+sass@1.57.1 apps/electron: specifiers: @@ -205,7 +207,7 @@ importers: shx: ^0.3.4 ts-node: ^10.9.1 typescript: ^4.9.4 - vitest: ^0.27.1 + vitest: ^0.29.8 ws: ^8.13.0 dependencies: '@sentry/node': 7.30.0 @@ -240,7 +242,7 @@ importers: shx: 0.3.4 ts-node: 10.9.1_qacynw5b4krdzzrloasmebsynu typescript: 4.9.4 - vitest: 0.27.1 + vitest: 0.29.8 packages/types: specifiers: @@ -267,7 +269,7 @@ importers: nanoid: ^4.0.1 prettier: ^2.8.3 typescript: ^4.9.4 - vitest: ^0.27.1 + vitest: ^0.29.8 dependencies: luxon: 3.3.0 nanoid: 4.0.1 @@ -281,7 +283,7 @@ importers: eslint-plugin-simple-import-sort: 8.0.0_eslint@8.31.0 prettier: 2.8.3 typescript: 4.9.4 - vitest: 0.27.1 + vitest: 0.29.8 packages: @@ -3405,6 +3407,37 @@ packages: - supports-color dev: true + /@vitest/expect/0.29.8: + resolution: {integrity: sha512-xlcVXn5I5oTq6NiZSY3ykyWixBxr5mG8HYtjvpgg6KaqHm0mvhX18xuwl5YGxIRNt/A5jidd7CWcNHrSvgaQqQ==} + dependencies: + '@vitest/spy': 0.29.8 + '@vitest/utils': 0.29.8 + chai: 4.3.7 + dev: true + + /@vitest/runner/0.29.8: + resolution: {integrity: sha512-FzdhnRDwEr/A3Oo1jtIk/B952BBvP32n1ObMEb23oEJNO+qO5cBet6M2XWIDQmA7BDKGKvmhUf2naXyp/2JEwQ==} + dependencies: + '@vitest/utils': 0.29.8 + p-limit: 4.0.0 + pathe: 1.1.0 + dev: true + + /@vitest/spy/0.29.8: + resolution: {integrity: sha512-VdjBe9w34vOMl5I5mYEzNX8inTxrZ+tYUVk9jxaZJmHFwmDFC/GV3KBFTA/JKswr3XHvZL+FE/yq5EVhb6pSAw==} + dependencies: + tinyspy: 1.0.2 + dev: true + + /@vitest/utils/0.29.8: + resolution: {integrity: sha512-qGzuf3vrTbnoY+RjjVVIBYfuWMjn3UMUqyQtdGNZ6ZIIyte7B37exj6LaVkrZiUTvzSadVvO/tJm8AEgbGCBPg==} + dependencies: + cli-truncate: 3.1.0 + diff: 5.1.0 + loupe: 2.3.6 + pretty-format: 27.5.1 + dev: true + /@zag-js/element-size/0.3.1: resolution: {integrity: sha512-jR5j4G//bRzcxwAACWi9EfITnwjNmn10LxF4NmALrdZU7/PNWP3uUCdhCxd/0SCyeiJXUl0yvD57rWAbKPs1nw==} dev: false @@ -3508,6 +3541,11 @@ packages: engines: {node: '>=8'} dev: true + /ansi-regex/6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + dev: true + /ansi-styles/3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} engines: {node: '>=4'} @@ -3526,6 +3564,11 @@ packages: engines: {node: '>=10'} dev: true + /ansi-styles/6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + dev: true + /anymatch/3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -4044,6 +4087,14 @@ packages: dev: true optional: true + /cli-truncate/3.1.0: + resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + slice-ansi: 5.0.0 + string-width: 5.1.2 + dev: true + /cliui/8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -4454,6 +4505,11 @@ packages: engines: {node: '>=0.3.1'} dev: true + /diff/5.1.0: + resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==} + engines: {node: '>=0.3.1'} + dev: true + /dir-compare/2.4.0: resolution: {integrity: sha512-l9hmu8x/rjVC9Z2zmGzkhOEowZvW7pmYws5CWHutg8u1JgvsKWMx7Q/UODeu4djLZ4FgW5besw5yvMQnBHzuCA==} hasBin: true @@ -4551,6 +4607,10 @@ packages: resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==} dev: true + /eastasianwidth/0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + dev: true + /ee-first/1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} dev: false @@ -4635,6 +4695,10 @@ packages: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} dev: true + /emoji-regex/9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + dev: true + /encodeurl/1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} @@ -5973,6 +6037,11 @@ packages: engines: {node: '>=8'} dev: true + /is-fullwidth-code-point/4.0.0: + resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} + engines: {node: '>=12'} + dev: true + /is-glob/4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -6633,7 +6702,7 @@ packages: resolution: {integrity: sha512-cwzBrBfwGC1gYJyfcy8TcZU1f+dbH/T+TuOhtYP2wLv/Fb51/uV7HJQfBPtEupZ2ORLRU1EKFS/QfS3eo9+kBQ==} dependencies: acorn: 8.8.1 - pathe: 1.0.0 + pathe: 1.1.0 pkg-types: 1.0.1 ufo: 1.0.1 dev: true @@ -6936,6 +7005,13 @@ packages: yocto-queue: 0.1.0 dev: true + /p-limit/4.0.0: + resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + yocto-queue: 1.0.0 + dev: true + /p-locate/4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} @@ -7028,12 +7104,8 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - /pathe/0.2.0: - resolution: {integrity: sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==} - dev: true - - /pathe/1.0.0: - resolution: {integrity: sha512-nPdMG0Pd09HuSsr7QOKUXO2Jr9eqaDiZvDwdyIhNG5SHYujkQHYKDfGQkulBxvbDHz8oHLsTgKN86LSwYzSHAg==} + /pathe/1.1.0: + resolution: {integrity: sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==} dev: true /pathval/1.1.1: @@ -7074,7 +7146,7 @@ packages: dependencies: jsonc-parser: 3.2.0 mlly: 1.1.0 - pathe: 1.0.0 + pathe: 1.1.0 dev: true /playwright-core/1.30.0: @@ -7860,6 +7932,14 @@ packages: is-fullwidth-code-point: 3.0.0 dev: true + /slice-ansi/5.0.0: + resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 4.0.0 + dev: true + /smart-buffer/4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} @@ -7968,6 +8048,10 @@ packages: engines: {node: '>= 0.8'} dev: false + /std-env/3.3.2: + resolution: {integrity: sha512-uUZI65yrV2Qva5gqE0+A7uVAvO40iPo6jGhs7s8keRfHCmtg+uB2X6EiLGCI9IgL1J17xGhvoOqSz79lzICPTA==} + dev: true + /steno/3.0.0: resolution: {integrity: sha512-uZtn7Ht9yXLiYgOsmo8btj4+f7VxyYheMt8g6F1ANjyqByQXEE2Gygjgenp3otHH1TlHsS4JAaRGv5wJ1wvMNw==} engines: {node: '>=14.16'} @@ -8000,6 +8084,15 @@ packages: strip-ansi: 6.0.1 dev: true + /string-width/5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.0.1 + dev: true + /string.prototype.matchall/4.0.8: resolution: {integrity: sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==} dependencies: @@ -8046,6 +8139,13 @@ packages: ansi-regex: 5.0.1 dev: true + /strip-ansi/7.0.1: + resolution: {integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==} + engines: {node: '>=12'} + dependencies: + ansi-regex: 6.0.1 + dev: true + /strip-final-newline/2.0.0: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} @@ -8292,8 +8392,8 @@ packages: resolution: {integrity: sha512-hGYWYBMPr7p4g5IarQE7XhlyWveh1EKhy4wUBS1LrHXCKYgvz+4/jCqgmJqZxxldesn05vccrtME2RLLZNW7iA==} dev: true - /tinypool/0.3.0: - resolution: {integrity: sha512-NX5KeqHOBZU6Bc0xj9Vr5Szbb1j8tUHIeD18s41aDJaPeC5QTdEhK0SpdpUrZlj2nv5cctNcSjaKNanXlfcVEQ==} + /tinypool/0.4.0: + resolution: {integrity: sha512-2ksntHOKf893wSAH4z/+JbPpi92esw8Gn9N2deXX+B0EO92hexAVI9GIZZPx7P5aYo5KULfeOSt3kMOmSOy6uA==} engines: {node: '>=14.0.0'} dev: true @@ -8760,18 +8860,16 @@ packages: dev: true optional: true - /vite-node/0.27.1_@types+node@18.11.18: - resolution: {integrity: sha512-d6+ue/3NzsfndWaPbYh/bFkHbmAWfDXI4B874zRx+WREnG6CUHUbBC8lKaRYZjeR6gCPN5m1aVNNRXBYICA9XA==} + /vite-node/0.29.8_@types+node@18.11.18: + resolution: {integrity: sha512-b6OtCXfk65L6SElVM20q5G546yu10/kNrhg08afEoWlFRJXFq9/6glsvSVY+aI6YeC1tu2TtAqI2jHEQmOmsFw==} engines: {node: '>=v14.16.0'} hasBin: true dependencies: cac: 6.7.14 debug: 4.3.4 mlly: 1.1.0 - pathe: 0.2.0 + pathe: 1.1.0 picocolors: 1.0.0 - source-map: 0.6.1 - source-map-support: 0.5.21 vite: 4.0.4_@types+node@18.11.18 transitivePeerDependencies: - '@types/node' @@ -8783,6 +8881,27 @@ packages: - terser dev: true + /vite-node/0.29.8_ovmyjmuuyckt3r3gpaexj2onji: + resolution: {integrity: sha512-b6OtCXfk65L6SElVM20q5G546yu10/kNrhg08afEoWlFRJXFq9/6glsvSVY+aI6YeC1tu2TtAqI2jHEQmOmsFw==} + engines: {node: '>=v14.16.0'} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.4 + mlly: 1.1.0 + pathe: 1.1.0 + picocolors: 1.0.0 + vite: 4.0.4_ovmyjmuuyckt3r3gpaexj2onji + transitivePeerDependencies: + - '@types/node' + - less + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + /vite-plugin-svgr/2.4.0_vite@4.0.4: resolution: {integrity: sha512-q+mJJol6ThvqkkJvvVFEndI4EaKIjSI0I3jNFgSoC9fXAz1M7kYTVUin8fhUsFojFDKZ9VHKtX6NXNaOLpbsHA==} peerDependencies: @@ -8844,6 +8963,41 @@ packages: fsevents: 2.3.2 dev: true + /vite/4.0.4_ovmyjmuuyckt3r3gpaexj2onji: + resolution: {integrity: sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==} + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true + peerDependencies: + '@types/node': '>= 14' + less: '*' + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': 18.11.18 + esbuild: 0.16.15 + postcss: 8.4.21 + resolve: 1.22.1 + rollup: 3.9.1 + sass: 1.57.1 + optionalDependencies: + fsevents: 2.3.2 + dev: true + /vite/4.0.4_sass@1.57.1: resolution: {integrity: sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==} engines: {node: ^14.18.0 || >=16.0.0} @@ -8878,8 +9032,8 @@ packages: fsevents: 2.3.2 dev: true - /vitest/0.27.1: - resolution: {integrity: sha512-1sIpQ1DVFTEn7c1ici1XHcVfdU4nKiBmPtPAtGKJJJLuJjojTv/OHGgcf69P57alM4ty8V4NMv+7Yoi5Cxqx9g==} + /vitest/0.29.8: + resolution: {integrity: sha512-JIAVi2GK5cvA6awGpH0HvH/gEG9PZ0a/WoxdiV3PmqK+3CjQMf8c+J/Vhv4mdZ2nRyXFw66sAg6qz7VNkaHfDQ==} engines: {node: '>=v14.16.0'} hasBin: true peerDependencies: @@ -8888,6 +9042,9 @@ packages: '@vitest/ui': '*' happy-dom: '*' jsdom: '*' + playwright: '*' + safaridriver: '*' + webdriverio: '*' peerDependenciesMeta: '@edge-runtime/vm': optional: true @@ -8899,24 +9056,101 @@ packages: optional: true jsdom: optional: true + playwright: + optional: true + safaridriver: + optional: true + webdriverio: + optional: true dependencies: '@types/chai': 4.3.4 '@types/chai-subset': 1.3.3 '@types/node': 18.11.18 + '@vitest/expect': 0.29.8 + '@vitest/runner': 0.29.8 + '@vitest/spy': 0.29.8 + '@vitest/utils': 0.29.8 acorn: 8.8.1 acorn-walk: 8.2.0 cac: 6.7.14 chai: 4.3.7 debug: 4.3.4 local-pkg: 0.4.2 + pathe: 1.1.0 picocolors: 1.0.0 source-map: 0.6.1 + std-env: 3.3.2 strip-literal: 1.0.0 tinybench: 2.3.1 - tinypool: 0.3.0 + tinypool: 0.4.0 tinyspy: 1.0.2 vite: 4.0.4_@types+node@18.11.18 - vite-node: 0.27.1_@types+node@18.11.18 + vite-node: 0.29.8_@types+node@18.11.18 + why-is-node-running: 2.2.2 + transitivePeerDependencies: + - less + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + + /vitest/0.29.8_jsdom@21.1.0+sass@1.57.1: + resolution: {integrity: sha512-JIAVi2GK5cvA6awGpH0HvH/gEG9PZ0a/WoxdiV3PmqK+3CjQMf8c+J/Vhv4mdZ2nRyXFw66sAg6qz7VNkaHfDQ==} + engines: {node: '>=v14.16.0'} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@vitest/browser': '*' + '@vitest/ui': '*' + happy-dom: '*' + jsdom: '*' + playwright: '*' + safaridriver: '*' + webdriverio: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + playwright: + optional: true + safaridriver: + optional: true + webdriverio: + optional: true + dependencies: + '@types/chai': 4.3.4 + '@types/chai-subset': 1.3.3 + '@types/node': 18.11.18 + '@vitest/expect': 0.29.8 + '@vitest/runner': 0.29.8 + '@vitest/spy': 0.29.8 + '@vitest/utils': 0.29.8 + acorn: 8.8.1 + acorn-walk: 8.2.0 + cac: 6.7.14 + chai: 4.3.7 + debug: 4.3.4 + jsdom: 21.1.0 + local-pkg: 0.4.2 + pathe: 1.1.0 + picocolors: 1.0.0 + source-map: 0.6.1 + std-env: 3.3.2 + strip-literal: 1.0.0 + tinybench: 2.3.1 + tinypool: 0.4.0 + tinyspy: 1.0.2 + vite: 4.0.4_ovmyjmuuyckt3r3gpaexj2onji + vite-node: 0.29.8_ovmyjmuuyckt3r3gpaexj2onji why-is-node-running: 2.2.2 transitivePeerDependencies: - less @@ -9205,6 +9439,11 @@ packages: engines: {node: '>=10'} dev: true + /yocto-queue/1.0.0: + resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} + engines: {node: '>=12.20'} + dev: true + /zustand/4.3.6_react@18.2.0: resolution: {integrity: sha512-6J5zDxjxLE+yukC2XZWf/IyWVKnXT9b9HUv09VJ/bwGCpKNcaTqp7Ws28Xr8jnbvnZcdRaidztAPsXFBIqufiw==} engines: {node: '>=12.7.0'}