diff --git a/.github/workflows/build_test_lint.yml b/.github/workflows/build_test_lint.yml index ef1065150..1495888f5 100644 --- a/.github/workflows/build_test_lint.yml +++ b/.github/workflows/build_test_lint.yml @@ -153,7 +153,6 @@ jobs: - name: Build workspace packages run: | npm run build -w packages/sdc-populate - npm run build -w packages/testing-toolkit - name: Install Playwright browsers run: npx playwright install --with-deps diff --git a/apps/smart-forms-app/jest.config.js b/apps/smart-forms-app/jest.config.js deleted file mode 100644 index a58c252f8..000000000 --- a/apps/smart-forms-app/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -/** @type {import('ts-jest').JestConfigWithTsJest} */ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node' -}; diff --git a/apps/smart-forms-app/jest.config.ts b/apps/smart-forms-app/jest.config.ts deleted file mode 100644 index 78c717cbb..000000000 --- a/apps/smart-forms-app/jest.config.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2025 Commonwealth Scientific and Industrial Research - * Organisation (CSIRO) ABN 41 687 119 230. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { Config } from 'jest'; - -const config: Config = { - verbose: true, - roots: ['src'], - preset: 'ts-jest', - testEnvironment: 'jsdom', - setupFilesAfterEnv: ['./src/setup-jest.ts'], - transform: { - '^.+\\.(ts|tsx)$': ['ts-jest', { - useESM: true, - isolatedModules: true, - }] - }, - transformIgnorePatterns: [ - '/node_modules/(?!(@aehrc|@fontsource)/)' - ], - moduleNameMapper: { - '^@fontsource/(.*)$': '/__mocks__/emptyModule.js' - }, - // Exclude "spec" folder - testRegex: '(/__tests__/.*|(\\.|/)(test))\\.(ts|tsx)?$', - moduleFileExtensions: ['ts', 'tsx', 'js', 'json', 'node'], - collectCoverage: true, - collectCoverageFrom: [ - '**/api/*.{ts,tsx}', - '**/hooks/*.{ts,tsx}', - '**/utils/*.{ts,tsx}', - '**/contexts/*.{ts,tsx}', - '!**/*.config.{ts,tsx,js,jsx}', // exclude config files - '!**/*.test.{ts,tsx}', // exclude test files - '!**/*.spec.{ts,tsx}', // exclude spec files - '!**/*.styles.{ts,tsx}', // exclude style files - '!**/*.interface.{ts,tsx}', // exclude interface files - '!**/*.d.ts', // exclude declaration files - '!**/e2e/**', // exclude e2e files - '!src/test/data-shared/**', // explicitly exclude src/test/data-shared - '!src/features/standalone/**', // explicitly exclude src/features/standalone - '!src/theme/**', // explicitly exclude src/theme - '!src/globals.ts', // explicitly exclude src/globals.ts - '!src/utils/dayjsExtend.ts', // explicitly exclude src/utils/dayjsExtend.ts - '!src/stores/selector.ts', // explicitly exclude src/stores/selector.ts - ], - coverageThreshold: { - "global": { - "statements": 80, - "branches": 75, - "functions": 80, - "lines": 80 - } - }, - clearMocks: true, - coverageDirectory: 'coverage' -}; - -export default config; diff --git a/apps/smart-forms-app/package.json b/apps/smart-forms-app/package.json index cb51064fd..f18fba2f9 100644 --- a/apps/smart-forms-app/package.json +++ b/apps/smart-forms-app/package.json @@ -8,7 +8,7 @@ "dev": "vite", "start": "vite", "build": "tsc && CI=false vite build", - "test": "TZ=Australia/Sydney jest --config ./jest.config.ts --coverage", + "test": "TZ=Australia/Sydney vitest run", "preview": "vite preview --port 4173", "playwright": "npx playwright test", "playwright-ui": "npx playwright test --ui" @@ -70,6 +70,7 @@ "react-syntax-highlighter": "^15.6.6", "react-to-print": "^3.1.1", "usehooks-ts": "^3.1.1", + "vitest": "^3.2.4", "zustand": "^5.0.8" }, "devDependencies": { @@ -95,6 +96,7 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", "eslint-plugin-storybook": "^9.1.6", + "expect-type": "^1.2.2", "jest": "^29.7.0", "jsdom": "^26.1.0", "prettier": "^3.6.2", @@ -102,6 +104,7 @@ "typescript": "^5.8.3", "vite": "^5.4.19", "vite-plugin-svgr": "^4.3.0", + "vitest": "^3.2.4", "yui-lint": "^0.2.0" }, "eslintConfig": { diff --git a/apps/smart-forms-app/src/test/questionnaireRenderer.test.tsx b/apps/smart-forms-app/src/test/questionnaireRenderer.test.tsx new file mode 100644 index 000000000..2b1165b6d --- /dev/null +++ b/apps/smart-forms-app/src/test/questionnaireRenderer.test.tsx @@ -0,0 +1,74 @@ +import { render } from '@testing-library/react'; +import { evaluate } from 'fhirpath'; +import type { Questionnaire, QuestionnaireResponse } from 'fhir/r4'; +import aboriginalForm from '../data/resources/Questionnaire/Questionnaire-AboriginalTorresStraitIslanderHealthCheckAssembled-0.1.0.json'; +import { QueryClientProvider } from '@tanstack/react-query'; +import { testUtils } from '@aehrc/smart-forms-renderer'; +import { vi } from 'vitest'; + +import { + BaseRenderer, + questionnaireResponseStore, + RendererThemeProvider, + useBuildForm, + useRendererQueryClient +} from '@aehrc/smart-forms-renderer'; + +const { inputText, inputDate, inputInteger, checkRadioOption } = testUtils; +vi.mock('fhirclient', () => ({ + client: () => ({ + request: vi.fn(() => Promise.resolve({})) + }) +})); + +export async function getAnswerRecursiveByLabel(text: string) { + const qr = questionnaireResponseStore.getState().updatableResponse; + console.log(1); + const result = await evaluate( + qr, + `QuestionnaireResponse.repeat(item).where((text = '${text}')).answer` + ); + return result; +} + +test('behaviour-test-example', async () => { + const form = aboriginalForm as Questionnaire; + + const { container } = render(); + await new Promise((resolve) => setTimeout(resolve, 1000)); + expect(container.innerHTML).toContain('Patient Details'); + + await inputText(container, 'Name', 'David'); + await inputDate(container, 'Date of birth', '11/11/2021'); + await inputInteger(container, 'Age', 24); + await checkRadioOption(container, 'Registered for NDIS', 'Yes'); + await inputText(container, 'NDIS Number', 'Some text'); +}); + +interface BuildFormWrapperForStorybookProps { + questionnaire: Questionnaire; + questionnaireResponse?: QuestionnaireResponse; +} + +function BuildFormWrapperForStorybook(props: BuildFormWrapperForStorybookProps) { + const { questionnaire, questionnaireResponse } = props; + const queryClient = useRendererQueryClient(); + const isBuilding = useBuildForm( + questionnaire, + questionnaireResponse, + undefined, + 'https://r4.ontoserver.csiro.au/fhir' + ); + + if (isBuilding) { + return
Loading...
; + } + + return ( + + + + + + ); +} diff --git a/apps/smart-forms-app/vitest.config.ts b/apps/smart-forms-app/vitest.config.ts new file mode 100644 index 000000000..6ce6c75bb --- /dev/null +++ b/apps/smart-forms-app/vitest.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + testTimeout:40000, + environment: 'jsdom', + include: ['**/__tests__/**/*.{test,spec}.{js,ts,jsx,tsx}', '**/*.{test,spec}.{js,ts,jsx,tsx}'], + exclude: ['**/e2e/**', '**/node_modules/**'], + coverage: { + provider: 'v8', + reporter: ['text', 'json', 'html'], + exclude: ['**/e2e/**', '**/node_modules/**'], + }, + }, +}); diff --git a/package-lock.json b/package-lock.json index fae05c761..fad4c3a22 100644 --- a/package-lock.json +++ b/package-lock.json @@ -76,6 +76,7 @@ "react-syntax-highlighter": "^15.6.6", "react-to-print": "^3.1.1", "usehooks-ts": "^3.1.1", + "vitest": "^3.2.4", "zustand": "^5.0.8" }, "devDependencies": { @@ -101,6 +102,7 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", "eslint-plugin-storybook": "^9.1.6", + "expect-type": "^1.2.2", "jest": "^29.7.0", "jsdom": "^26.1.0", "prettier": "^3.6.2", @@ -108,6 +110,7 @@ "typescript": "^5.8.3", "vite": "^5.4.19", "vite-plugin-svgr": "^4.3.0", + "vitest": "^3.2.4", "yui-lint": "^0.2.0" } }, @@ -13535,6 +13538,46 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, "node_modules/@vitest/spy": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", @@ -13548,6 +13591,47 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@vitest/ui": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-3.2.4.tgz", + "integrity": "sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@vitest/utils": "3.2.4", + "fflate": "^0.8.2", + "flatted": "^3.3.3", + "pathe": "^2.0.3", + "sirv": "^3.0.1", + "tinyglobby": "^0.2.14", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "3.2.4" + } + }, + "node_modules/@vitest/ui/node_modules/sirv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz", + "integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@vitest/utils": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", @@ -18439,9 +18523,10 @@ } }, "node_modules/es-module-lexer": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", - "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==" + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "license": "MIT" }, "node_modules/es-object-atoms": { "version": "1.1.1", @@ -19306,6 +19391,16 @@ "dev": true, "license": "MIT" }, + "node_modules/expect-type": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", + "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/expo-modules-autolinking": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-0.0.3.tgz", @@ -19648,10 +19743,14 @@ "peer": true }, "node_modules/fdir": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", - "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -19680,6 +19779,15 @@ "optional": true, "peer": true }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/fhir-extension-helpers": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/fhir-extension-helpers/-/fhir-extension-helpers-0.3.0.tgz", @@ -34887,6 +34995,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -35259,6 +35374,13 @@ "node": ">=8" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, "node_modules/stackframe": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", @@ -35698,6 +35820,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-literal": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", + "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/structured-headers": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/structured-headers/-/structured-headers-0.4.1.tgz", @@ -36341,6 +36483,13 @@ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, "node_modules/tinyexec": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", @@ -36348,12 +36497,13 @@ "dev": true }, "node_modules/tinyglobby": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz", - "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==", + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", "dev": true, + "license": "MIT", "dependencies": { - "fdir": "^6.4.3", + "fdir": "^6.4.4", "picomatch": "^4.0.2" }, "engines": { @@ -36364,9 +36514,9 @@ } }, "node_modules/tinypool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.0.tgz", - "integrity": "sha512-7CotroY9a8DKsKprEy/a14aCCm8jYVmR7aFy4fpkZM8sdpNJbKkixuNjgM50yCmip2ezc8z4N7k3oe2+rfRJCQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", "license": "MIT", "engines": { "node": "^18.0.0 || >=20.0.0" @@ -37848,6 +37998,29 @@ } } }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/vite-plugin-svgr": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.3.0.tgz", @@ -38306,6 +38479,89 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, "node_modules/vlq": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", @@ -39387,6 +39643,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/widest-line": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", diff --git a/packages/smart-forms-renderer/src/components/FormComponents/AttachmentItem/AttachmentFieldWrapper.tsx b/packages/smart-forms-renderer/src/components/FormComponents/AttachmentItem/AttachmentFieldWrapper.tsx index 4996025fa..e4004acb9 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/AttachmentItem/AttachmentFieldWrapper.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/AttachmentItem/AttachmentFieldWrapper.tsx @@ -75,6 +75,7 @@ function AttachmentFieldWrapper(props: AttachmentFieldWrapperProps) { onFocusLinkId(qItem.linkId)}> onFocusLinkId(qItem.linkId)}> onFocusLinkId(qItem.linkId)}> onFocusLinkId(qItem.linkId)}> onFocusLinkId(qItem.linkId)}> onFocusLinkId(qItem.linkId)}> )} /> diff --git a/packages/smart-forms-renderer/src/components/FormComponents/DateTimeItems/CustomDateItem/CustomDateItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/DateTimeItems/CustomDateItem/CustomDateItem.tsx index 111859bdf..c249c3c15 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/DateTimeItems/CustomDateItem/CustomDateItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/DateTimeItems/CustomDateItem/CustomDateItem.tsx @@ -152,6 +152,7 @@ function CustomDateItem(props: BaseItemProps) { onFocusLinkId(qItem.linkId)}> onFocusLinkId(qItem.linkId)}> onFocusLinkId(qItem.linkId)}> onFocusLinkId(qItem.linkId)}> onFocusLinkId(qItem.linkId)} style={parentStyles || undefined}> {itemTextToDisplay ? ( diff --git a/packages/smart-forms-renderer/src/components/FormComponents/IntegerItem/IntegerItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/IntegerItem/IntegerItem.tsx index 8af37052f..cb5d33396 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/IntegerItem/IntegerItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/IntegerItem/IntegerItem.tsx @@ -157,6 +157,7 @@ function IntegerItem(props: BaseItemProps) { onFocusLinkId(qItem.linkId)}> onFocusLinkId(qItem.linkId)}> onFocusLinkId(qItem.linkId)}> onFocusLinkId(qItem.linkId)}> onFocusLinkId(qItem.linkId)}> onFocusLinkId(qItem.linkId)}> onFocusLinkId(qItem.linkId)}> onFocusLinkId(qItem.linkId)}> onFocusLinkId(qItem.linkId)}> onFocusLinkId(qItem.linkId)}> onFocusLinkId(qItem.linkId)}> onFocusLinkId(qItem.linkId)}> onFocusLinkId(qItem.linkId)} style={parentStyles || undefined}> {itemTextToDisplay ? ( diff --git a/packages/smart-forms-renderer/src/components/FormComponents/TextItem/TextItem.tsx b/packages/smart-forms-renderer/src/components/FormComponents/TextItem/TextItem.tsx index f0232c13c..c4050e3df 100644 --- a/packages/smart-forms-renderer/src/components/FormComponents/TextItem/TextItem.tsx +++ b/packages/smart-forms-renderer/src/components/FormComponents/TextItem/TextItem.tsx @@ -154,6 +154,7 @@ function TextItem(props: BaseItemProps) { onFocusLinkId(qItem.linkId)}> onFocusLinkId(qItem.linkId)}> onFocusLinkId(qItem.linkId)}> { - const element = await findByLinkId(canvasElement, targetLinkId); + const element = await findByLinkIdOrLabel(canvasElement, targetLinkId); const input = element.querySelector('input'); expect(input).toBeChecked(); diff --git a/packages/smart-forms-renderer/src/stories/itemTypes/Choice.stories.tsx b/packages/smart-forms-renderer/src/stories/itemTypes/Choice.stories.tsx index ceb4334fd..8f60a0f31 100644 --- a/packages/smart-forms-renderer/src/stories/itemTypes/Choice.stories.tsx +++ b/packages/smart-forms-renderer/src/stories/itemTypes/Choice.stories.tsx @@ -23,7 +23,7 @@ import { } from '../assets/questionnaires'; import { chooseSelectOption, - findByLinkId, + findByLinkIdOrLabel, getAnswers, getInputText, qrFactory, @@ -100,7 +100,7 @@ export const ChoiceAnswerOptionBasic: Story = { const resultAfterClear = await getAnswers(targetLinkId); expect(resultAfterClear).toHaveLength(0); - const elementAfterClear = await findByLinkId(canvasElement, targetLinkId); + const elementAfterClear = await findByLinkIdOrLabel(canvasElement, targetLinkId); const input = elementAfterClear.querySelector('textarea'); expect(input?.value).toBe(''); } diff --git a/packages/smart-forms-renderer/src/stories/itemTypes/Decimal.stories.tsx b/packages/smart-forms-renderer/src/stories/itemTypes/Decimal.stories.tsx index c947fc45e..a8d2918fa 100644 --- a/packages/smart-forms-renderer/src/stories/itemTypes/Decimal.stories.tsx +++ b/packages/smart-forms-renderer/src/stories/itemTypes/Decimal.stories.tsx @@ -19,7 +19,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import BuildFormWrapperForStorybook from '../storybookWrappers/BuildFormWrapperForStorybook'; import { qCalculatedExpressionBMICalculator } from '../assets/questionnaires'; import { - findByLinkId, + findByLinkIdOrLabel, getAnswers, getInputText, inputDecimal, @@ -84,7 +84,7 @@ export const DecimalBasic: Story = { const resultAfterClear = await getAnswers(targetLinkId); expect(resultAfterClear).toHaveLength(0); - const elementAfterClear = await findByLinkId(canvasElement, targetLinkId); + const elementAfterClear = await findByLinkIdOrLabel(canvasElement, targetLinkId); const input = elementAfterClear.querySelector('input'); expect(input?.getAttribute('value')).toBe(''); } diff --git a/packages/smart-forms-renderer/src/stories/itemTypes/Integer.stories.tsx b/packages/smart-forms-renderer/src/stories/itemTypes/Integer.stories.tsx index 5170115d8..3a3920a1e 100644 --- a/packages/smart-forms-renderer/src/stories/itemTypes/Integer.stories.tsx +++ b/packages/smart-forms-renderer/src/stories/itemTypes/Integer.stories.tsx @@ -19,7 +19,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import BuildFormWrapperForStorybook from '../storybookWrappers/BuildFormWrapperForStorybook'; import { qIntegerCalculation } from '../assets/questionnaires'; import { - findByLinkId, + findByLinkIdOrLabel, getAnswers, getInputText, inputInteger, @@ -83,7 +83,7 @@ export const IntegerBasic: Story = { await new Promise((resolve) => setTimeout(resolve, 500)); const resultAfterClear = await getAnswers(targetLinkId); expect(resultAfterClear).toHaveLength(0); - const elementAfterClear = await findByLinkId(canvasElement, targetLinkId); + const elementAfterClear = await findByLinkIdOrLabel(canvasElement, targetLinkId); const input = elementAfterClear.querySelector('input'); expect(input?.getAttribute('value')).toBe(''); } diff --git a/packages/smart-forms-renderer/src/stories/itemTypes/OpenChoice.stories.tsx b/packages/smart-forms-renderer/src/stories/itemTypes/OpenChoice.stories.tsx index 3b3cd1e95..adc849f3e 100644 --- a/packages/smart-forms-renderer/src/stories/itemTypes/OpenChoice.stories.tsx +++ b/packages/smart-forms-renderer/src/stories/itemTypes/OpenChoice.stories.tsx @@ -21,7 +21,7 @@ import { qOpenChoiceAnswerAutoCompleteFromValueSet } from '../assets/questionnai import { checkRadioOption, chooseSelectOption, - findByLinkId, + findByLinkIdOrLabel, getAnswers, inputOpenChoiceOtherText, itemControlExtFactory, @@ -97,7 +97,7 @@ export const OpenChoiceAnswerOptionBasic: Story = { await new Promise((resolve) => setTimeout(resolve, 500)); const resultAfterClear = await getAnswers(targetLinkId); expect(resultAfterClear).toHaveLength(0); - const elementAfterClear = await findByLinkId(canvasElement, targetLinkId); + const elementAfterClear = await findByLinkIdOrLabel(canvasElement, targetLinkId); const input = elementAfterClear.querySelector('input'); expect(input).not.toBeChecked(); @@ -127,7 +127,7 @@ export const OpenChoiceAnswerOptionBasicOther: Story = { const qrAfterClear = await getAnswers(targetOtherLinkid); expect(qrAfterClear).toHaveLength(0); - const resultAfterClear = await findByLinkId(canvasElement, targetLinkId); + const resultAfterClear = await findByLinkIdOrLabel(canvasElement, targetLinkId); const input = resultAfterClear.querySelector('textarea'); expect(input?.value).toBe(''); } @@ -183,7 +183,7 @@ export const OpenChoiceAnswerValueSetBasic: Story = { const qrAfterClear = await getAnswers('state'); expect(qrAfterClear).toHaveLength(0); - const resultAfterClear = await findByLinkId(canvasElement, targetOperResId); + const resultAfterClear = await findByLinkIdOrLabel(canvasElement, targetOperResId); const input = resultAfterClear.querySelector('input'); expect(input).not.toBeChecked(); } @@ -206,7 +206,7 @@ export const OpenChoiceAnswerValueSetBasicResponse: Story = { questionnaireResponse: qrOpenChoiceAnswerValueSetBasicResponse }, play: async ({ canvasElement }) => { - const element = await findByLinkId(canvasElement, targetOperResId); + const element = await findByLinkIdOrLabel(canvasElement, targetOperResId); const input = element.querySelector( 'div[data-test="q-item-radio-open-label-box"] textarea' ) as HTMLTextAreaElement; diff --git a/packages/smart-forms-renderer/src/stories/itemTypes/Reference.stories.tsx b/packages/smart-forms-renderer/src/stories/itemTypes/Reference.stories.tsx index 5070d16e7..e77827da5 100644 --- a/packages/smart-forms-renderer/src/stories/itemTypes/Reference.stories.tsx +++ b/packages/smart-forms-renderer/src/stories/itemTypes/Reference.stories.tsx @@ -18,7 +18,12 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import BuildFormWrapperForStorybook from '../storybookWrappers/BuildFormWrapperForStorybook'; -import { findByLinkId, getAnswers, inputReference, questionnaireFactory } from '../testUtils'; +import { + findByLinkIdOrLabel, + getAnswers, + inputReference, + questionnaireFactory +} from '../testUtils'; import { expect, fireEvent } from 'storybook/test'; // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction#default-export @@ -64,7 +69,7 @@ export const ReferenceBasic: Story = { await new Promise((resolve) => setTimeout(resolve, 500)); const resultAfterClear = await getAnswers(targetLinkId); expect(resultAfterClear).toHaveLength(0); - const elementAfterClear = await findByLinkId(canvasElement, targetLinkId); + const elementAfterClear = await findByLinkIdOrLabel(canvasElement, targetLinkId); const input = elementAfterClear.querySelector('textarea'); expect(input?.value).toBe(''); } diff --git a/packages/smart-forms-renderer/src/stories/itemTypes/String.stories.tsx b/packages/smart-forms-renderer/src/stories/itemTypes/String.stories.tsx index 552350446..cb493a542 100644 --- a/packages/smart-forms-renderer/src/stories/itemTypes/String.stories.tsx +++ b/packages/smart-forms-renderer/src/stories/itemTypes/String.stories.tsx @@ -19,7 +19,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import BuildFormWrapperForStorybook from '../storybookWrappers/BuildFormWrapperForStorybook'; import { qStringCalculation } from '../assets/questionnaires'; import { - findByLinkId, + findByLinkIdOrLabel, getAnswers, getInputText, inputText, @@ -82,7 +82,7 @@ export const StringBasic: Story = { await new Promise((resolve) => setTimeout(resolve, 500)); const resultAfterClear = await getAnswers(targetLinkId); expect(resultAfterClear).toHaveLength(0); - const elementAfterClear = await findByLinkId(canvasElement, targetLinkId); + const elementAfterClear = await findByLinkIdOrLabel(canvasElement, targetLinkId); const input = elementAfterClear.querySelector('textarea'); expect(input?.value).toBe(''); } diff --git a/packages/smart-forms-renderer/src/stories/itemTypes/Text.stories.tsx b/packages/smart-forms-renderer/src/stories/itemTypes/Text.stories.tsx index 58ba50f62..9ff57e5cd 100644 --- a/packages/smart-forms-renderer/src/stories/itemTypes/Text.stories.tsx +++ b/packages/smart-forms-renderer/src/stories/itemTypes/Text.stories.tsx @@ -19,7 +19,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import BuildFormWrapperForStorybook from '../storybookWrappers/BuildFormWrapperForStorybook'; import { qTextCalculation } from '../assets/questionnaires'; import { - findByLinkId, + findByLinkIdOrLabel, getAnswers, getInputText, inputText, @@ -68,7 +68,7 @@ export const TextBasic: Story = { await new Promise((resolve) => setTimeout(resolve, 500)); const resultAfterClear = await getAnswers(targetLinkId); expect(resultAfterClear).toHaveLength(0); - const elementAfterClear = await findByLinkId(canvasElement, targetLinkId); + const elementAfterClear = await findByLinkIdOrLabel(canvasElement, targetLinkId); const input = elementAfterClear.querySelector('textarea'); expect(input?.value).toBe(''); } diff --git a/packages/smart-forms-renderer/src/stories/itemTypes/Url.stories.tsx b/packages/smart-forms-renderer/src/stories/itemTypes/Url.stories.tsx index e10cc1c12..4c614c1f4 100644 --- a/packages/smart-forms-renderer/src/stories/itemTypes/Url.stories.tsx +++ b/packages/smart-forms-renderer/src/stories/itemTypes/Url.stories.tsx @@ -18,7 +18,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import BuildFormWrapperForStorybook from '../storybookWrappers/BuildFormWrapperForStorybook'; -import { findByLinkId, getAnswers, inputUrl, questionnaireFactory } from '../testUtils'; +import { findByLinkIdOrLabel, getAnswers, inputUrl, questionnaireFactory } from '../testUtils'; import { expect, fireEvent } from 'storybook/test'; // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction#default-export @@ -64,7 +64,7 @@ export const UrlBasic: Story = { await new Promise((resolve) => setTimeout(resolve, 500)); const resultAfterClear = await getAnswers(targetLinkId); expect(resultAfterClear).toHaveLength(0); - const elementAfterClear = await findByLinkId(canvasElement, targetLinkId); + const elementAfterClear = await findByLinkIdOrLabel(canvasElement, targetLinkId); const input = elementAfterClear.querySelector('textarea'); expect(input?.value).toBe(''); } diff --git a/packages/smart-forms-renderer/src/stories/testUtils.ts b/packages/smart-forms-renderer/src/stories/testUtils.ts index d5fa0b0dd..f457af238 100644 --- a/packages/smart-forms-renderer/src/stories/testUtils.ts +++ b/packages/smart-forms-renderer/src/stories/testUtils.ts @@ -68,7 +68,7 @@ export async function inputText( linkId: string, text: string | boolean | number ) { - const questionElement = await findByLinkId(canvasElement, linkId); + const questionElement = await findByLinkIdOrLabel(canvasElement, linkId); const input = questionElement?.querySelector('input') ?? questionElement?.querySelector('textarea'); @@ -83,7 +83,7 @@ export async function inputText( await new Promise((resolve) => setTimeout(resolve, 500)); } export async function checkCheckBox(canvasElement: HTMLElement, linkId: string) { - const questionElement = await findByLinkId(canvasElement, linkId); + const questionElement = await findByLinkIdOrLabel(canvasElement, linkId); const input = questionElement?.querySelector('input') ?? questionElement?.querySelector('textarea'); @@ -104,7 +104,7 @@ export async function inputFile( url: string, filename: string ) { - const questionElement = await findByLinkId(canvasElement, linkId); + const questionElement = await findByLinkIdOrLabel(canvasElement, linkId); const input = questionElement?.querySelector('input'); const textareaUrl = questionElement?.querySelector(`textarea[data-test="q-item-attachment-url"]`); @@ -174,7 +174,7 @@ export async function inputDateTime( time: string, amPm: string ) { - const questionElement = await findByLinkId(canvasElement, linkId); + const questionElement = await findByLinkIdOrLabel(canvasElement, linkId); console.log(questionElement, 777); const inputDate = questionElement?.querySelector('div[data-test="date"] input'); const inputTime = questionElement?.querySelector('div[data-test="time"] input'); @@ -199,7 +199,7 @@ export async function inputDateTime( } export async function checkRadioOption(canvasElement: HTMLElement, linkId: string, text: string) { - const questionElement = await findByLinkId(canvasElement, linkId); + const questionElement = await findByLinkIdOrLabel(canvasElement, linkId); const radio = questionElement?.querySelector(`span[data-test="radio-single-${text}"] input`); if (!radio) { @@ -212,7 +212,7 @@ export async function checkRadioOption(canvasElement: HTMLElement, linkId: strin } export async function getInputText(canvasElement: HTMLElement, linkId: string) { - const questionElement = await findByLinkId(canvasElement, linkId); + const questionElement = await findByLinkIdOrLabel(canvasElement, linkId); const input = questionElement?.querySelector('input') ?? questionElement?.querySelector('textarea'); @@ -228,7 +228,7 @@ export async function chooseSelectOption( linkId: string, optionLabel: string ) { - const questionElement = await findByLinkId(canvasElement, linkId); + const questionElement = await findByLinkIdOrLabel(canvasElement, linkId); const input = questionElement.querySelector('input, textarea'); if (!input) { @@ -247,7 +247,7 @@ export async function chooseQuantityOption( quantity: number | string, quantityComparator?: string ) { - const questionElement = await findByLinkId(canvasElement, linkId); + const questionElement = await findByLinkIdOrLabel(canvasElement, linkId); const inputComaparator = questionElement.querySelector( 'div[data-test="q-item-quantity-comparator"] input' @@ -275,22 +275,34 @@ export async function chooseQuantityOption( await new Promise((resolve) => setTimeout(resolve, 500)); } -export async function findByLinkId(canvasElement: HTMLElement, linkId: string) { - const selector = `[data-linkid="${linkId}"]`; +export async function findByLinkIdOrLabel( + canvasElement: HTMLElement, + linkId: string +): Promise { + const selectorByLinkId = `[data-linkid="${linkId}"]`; + const selectorByLabel = `[data-label="${linkId}"]`; + return await waitFor(() => { - const el = canvasElement.querySelector(selector); + const el = + canvasElement.querySelector(selectorByLinkId) ?? + canvasElement.querySelector(selectorByLabel); + if (!el) { - throw new Error(`Element ${selector} not found`); + throw new Error( + `Element with selectors "${selectorByLinkId}" or "${selectorByLabel}" not found` + ); } + return el; }); } + export async function inputOpenChoiceOtherText( canvasElement: HTMLElement, linkId: string, text: string ) { - const questionElement = await findByLinkId(canvasElement, linkId); + const questionElement = await findByLinkIdOrLabel(canvasElement, linkId); const textarea = questionElement?.querySelector( 'div[data-test="q-item-radio-open-label-box"] textarea' diff --git a/packages/testing-toolkit/src/index.ts b/packages/testing-toolkit/src/index.ts index adf1f8078..bfa62d6ac 100644 --- a/packages/testing-toolkit/src/index.ts +++ b/packages/testing-toolkit/src/index.ts @@ -6,7 +6,7 @@ export async function inputText( linkId: string, text: string | boolean | number ) { - const questionElement = await findByLinkId(canvasElement, linkId); + const questionElement = await findByLinkIdAndLabel(canvasElement, linkId); const input = questionElement?.querySelector('input') ?? questionElement?.querySelector('textarea'); @@ -21,7 +21,7 @@ export async function inputText( await new Promise((resolve) => setTimeout(resolve, 500)); } export async function checkCheckBox(canvasElement: HTMLElement, linkId: string) { - const questionElement = await findByLinkId(canvasElement, linkId); + const questionElement = await findByLinkIdAndLabel(canvasElement, linkId); const input = questionElement?.querySelector('input') ?? questionElement?.querySelector('textarea'); @@ -42,7 +42,7 @@ export async function inputFile( url: string, filename: string ) { - const questionElement = await findByLinkId(canvasElement, linkId); + const questionElement = await findByLinkIdAndLabel(canvasElement, linkId); const input = questionElement?.querySelector('input'); const textareaUrl = questionElement?.querySelector(`textarea[data-test="q-item-attachment-url"]`); @@ -112,7 +112,7 @@ export async function inputDateTime( time: string, amPm: string ) { - const questionElement = await findByLinkId(canvasElement, linkId); + const questionElement = await findByLinkIdAndLabel(canvasElement, linkId); console.log(questionElement, 777); const inputDate = questionElement?.querySelector('div[data-test="date"] input'); const inputTime = questionElement?.querySelector('div[data-test="time"] input'); @@ -137,7 +137,7 @@ export async function inputDateTime( } export async function checkRadioOption(canvasElement: HTMLElement, linkId: string, text: string) { - const questionElement = await findByLinkId(canvasElement, linkId); + const questionElement = await findByLinkIdAndLabel(canvasElement, linkId); const radio = questionElement?.querySelector(`span[data-test="radio-single-${text}"] input`); if (!radio) { @@ -150,7 +150,7 @@ export async function checkRadioOption(canvasElement: HTMLElement, linkId: strin } export async function getInputText(canvasElement: HTMLElement, linkId: string) { - const questionElement = await findByLinkId(canvasElement, linkId); + const questionElement = await findByLinkIdAndLabel(canvasElement, linkId); const input = questionElement?.querySelector('input') ?? questionElement?.querySelector('textarea'); @@ -166,7 +166,7 @@ export async function chooseSelectOption( linkId: string, optionLabel: string ) { - const questionElement = await findByLinkId(canvasElement, linkId); + const questionElement = await findByLinkIdAndLabel(canvasElement, linkId); const input = questionElement.querySelector('input, textarea'); if (!input) { @@ -185,7 +185,7 @@ export async function chooseQuantityOption( quantity: number | string, quantityComparator?: string ) { - const questionElement = await findByLinkId(canvasElement, linkId); + const questionElement = await findByLinkIdAndLabel(canvasElement, linkId); const inputComaparator = questionElement.querySelector( 'div[data-test=""q-item-quantity-comparator""] input' @@ -213,22 +213,34 @@ export async function chooseQuantityOption( await new Promise((resolve) => setTimeout(resolve, 500)); } -export async function findByLinkId(canvasElement: HTMLElement, linkId: string) { - const selector = `[data-linkid="${linkId}"]`; +export async function findByLinkIdAndLabel( + canvasElement: HTMLElement, + linkId: string +): Promise { + const selectorByLinkId = `[data-linkid="${linkId}"]`; + const selectorByLabel = `[data-label="${linkId}"]`; + return await waitFor(() => { - const el = canvasElement.querySelector(selector); + const el = + canvasElement.querySelector(selectorByLinkId) ?? + canvasElement.querySelector(selectorByLabel); + if (!el) { - throw new Error(`Element ${selector} not found`); + throw new Error( + `Element with selectors "${selectorByLinkId}" or "${selectorByLabel}" not found` + ); } + return el; }); } + export async function inputOpenChoiceOtherText( canvasElement: HTMLElement, linkId: string, text: string ) { - const questionElement = await findByLinkId(canvasElement, linkId); + const questionElement = await findByLinkIdAndLabel(canvasElement, linkId); const textarea = questionElement?.querySelector( 'div[data-test="q-item-radio-open-label-box"] textarea'