diff --git a/.eslintignore b/.eslintignore index 22e24e7..40dbb32 100644 --- a/.eslintignore +++ b/.eslintignore @@ -9,4 +9,5 @@ __mocks__/ commitlint.config.js jest.config.cjs +jest.config.ssr.cjs *.d.ts diff --git a/jest.config.cjs b/configs/jest.config.cjs similarity index 80% rename from jest.config.cjs rename to configs/jest.config.cjs index 60a5e80..3092644 100644 --- a/jest.config.cjs +++ b/configs/jest.config.cjs @@ -15,7 +15,7 @@ module.exports = { testEnvironment: "jsdom", - setupFilesAfterEnv: [`${pkgRootPath}/../../jest.setup.ts`, "regenerator-runtime"], + setupFilesAfterEnv: [`${pkgRootPath}/../../configs/jest.setup.ts`, "regenerator-runtime"], moduleNameMapper: { "solid-js/web": `${solidjsPath}/web/dist/web.cjs`, @@ -23,6 +23,8 @@ module.exports = { "solid-js": `${solidjsPath}/dist/solid.cjs` }, + testMatch: ["**/test/*.test.(js|ts)?(x)"], + verbose: true, testTimeout: 30000 }; diff --git a/configs/jest.config.ssr.cjs b/configs/jest.config.ssr.cjs new file mode 100644 index 0000000..534b48b --- /dev/null +++ b/configs/jest.config.ssr.cjs @@ -0,0 +1,34 @@ +const pkgRootPath = ``; +const solidjsPath = `${pkgRootPath}/../../node_modules/solid-js`; + +/** @type {import('@jest/types').Config.InitialOptions} */ +module.exports = { + preset: "ts-jest", + + globals: { + "ts-jest": { + tsconfig: `${pkgRootPath}/tsconfig.json`, + babelConfig: { + presets: [ + "@babel/preset-env", + ["babel-preset-solid", { generate: "ssr", hydratable: true }] + ] + } + } + }, + + testEnvironment: "node", + + moduleNameMapper: { + "solid-js/web": `${solidjsPath}/web/dist/server.cjs`, + "solid-js/store": `${solidjsPath}/store/dist/server.cjs`, + "solid-js": `${solidjsPath}/dist/server.cjs` + }, + + setupFilesAfterEnv: [`${pkgRootPath}/../../configs/jest.setup.ssr.ts`], + + testMatch: ["**/test/ssr/*.test.(js|ts)?(x)"], + + verbose: true, + testTimeout: 30000 +}; diff --git a/configs/jest.setup.ssr.ts b/configs/jest.setup.ssr.ts new file mode 100644 index 0000000..2166e6f --- /dev/null +++ b/configs/jest.setup.ssr.ts @@ -0,0 +1,6 @@ +jest.mock("solid-js/web", () => ({ + ...jest.requireActual("solid-js/web"), + template: jest.fn() +})); + +export {}; diff --git a/jest.setup.ts b/configs/jest.setup.ts similarity index 100% rename from jest.setup.ts rename to configs/jest.setup.ts diff --git a/tsup.config.ts b/configs/tsup.config.ts similarity index 100% rename from tsup.config.ts rename to configs/tsup.config.ts diff --git a/vite.config.ts b/configs/vite.config.ts similarity index 100% rename from vite.config.ts rename to configs/vite.config.ts diff --git a/packages/breadcrumbs/dev/vite.config.ts b/packages/breadcrumbs/dev/vite.config.ts index 2f3fe11..6b5d194 100644 --- a/packages/breadcrumbs/dev/vite.config.ts +++ b/packages/breadcrumbs/dev/vite.config.ts @@ -1,3 +1,3 @@ -import { viteConfig } from "../../../vite.config"; +import { viteConfig } from "../../../configs/vite.config"; export default viteConfig; diff --git a/packages/breadcrumbs/jest.config.cjs b/packages/breadcrumbs/jest.config.cjs index c68d815..992a8b0 100644 --- a/packages/breadcrumbs/jest.config.cjs +++ b/packages/breadcrumbs/jest.config.cjs @@ -1,4 +1,4 @@ -const baseJest = require("../../jest.config.cjs"); +const baseJest = require("../../configs/jest.config.cjs"); module.exports = { ...baseJest diff --git a/packages/breadcrumbs/tsup.config.ts b/packages/breadcrumbs/tsup.config.ts index 9097772..066fc44 100644 --- a/packages/breadcrumbs/tsup.config.ts +++ b/packages/breadcrumbs/tsup.config.ts @@ -1,3 +1,3 @@ -import defaultConfig from "../../tsup.config"; +import defaultConfig from "../../configs/tsup.config"; export default defaultConfig; diff --git a/packages/breadcrumbs/vite.config.ts b/packages/breadcrumbs/vite.config.ts index c06c04b..b0cbe11 100644 --- a/packages/breadcrumbs/vite.config.ts +++ b/packages/breadcrumbs/vite.config.ts @@ -1,3 +1,3 @@ -import { viteConfig } from "../../vite.config"; +import { viteConfig } from "../../configs/vite.config"; export default viteConfig; diff --git a/packages/button/dev/vite.config.ts b/packages/button/dev/vite.config.ts index 2f3fe11..6b5d194 100644 --- a/packages/button/dev/vite.config.ts +++ b/packages/button/dev/vite.config.ts @@ -1,3 +1,3 @@ -import { viteConfig } from "../../../vite.config"; +import { viteConfig } from "../../../configs/vite.config"; export default viteConfig; diff --git a/packages/button/jest.config.cjs b/packages/button/jest.config.cjs index c68d815..992a8b0 100644 --- a/packages/button/jest.config.cjs +++ b/packages/button/jest.config.cjs @@ -1,4 +1,4 @@ -const baseJest = require("../../jest.config.cjs"); +const baseJest = require("../../configs/jest.config.cjs"); module.exports = { ...baseJest diff --git a/packages/button/tsup.config.ts b/packages/button/tsup.config.ts index 9097772..066fc44 100644 --- a/packages/button/tsup.config.ts +++ b/packages/button/tsup.config.ts @@ -1,3 +1,3 @@ -import defaultConfig from "../../tsup.config"; +import defaultConfig from "../../configs/tsup.config"; export default defaultConfig; diff --git a/packages/button/vite.config.ts b/packages/button/vite.config.ts index c06c04b..b0cbe11 100644 --- a/packages/button/vite.config.ts +++ b/packages/button/vite.config.ts @@ -1,3 +1,3 @@ -import { viteConfig } from "../../vite.config"; +import { viteConfig } from "../../configs/vite.config"; export default viteConfig; diff --git a/packages/checkbox/dev/vite.config.ts b/packages/checkbox/dev/vite.config.ts index 2f3fe11..6b5d194 100644 --- a/packages/checkbox/dev/vite.config.ts +++ b/packages/checkbox/dev/vite.config.ts @@ -1,3 +1,3 @@ -import { viteConfig } from "../../../vite.config"; +import { viteConfig } from "../../../configs/vite.config"; export default viteConfig; diff --git a/packages/checkbox/jest.config.cjs b/packages/checkbox/jest.config.cjs index c68d815..992a8b0 100644 --- a/packages/checkbox/jest.config.cjs +++ b/packages/checkbox/jest.config.cjs @@ -1,4 +1,4 @@ -const baseJest = require("../../jest.config.cjs"); +const baseJest = require("../../configs/jest.config.cjs"); module.exports = { ...baseJest diff --git a/packages/checkbox/tsup.config.ts b/packages/checkbox/tsup.config.ts index 9097772..066fc44 100644 --- a/packages/checkbox/tsup.config.ts +++ b/packages/checkbox/tsup.config.ts @@ -1,3 +1,3 @@ -import defaultConfig from "../../tsup.config"; +import defaultConfig from "../../configs/tsup.config"; export default defaultConfig; diff --git a/packages/checkbox/vite.config.ts b/packages/checkbox/vite.config.ts index c06c04b..b0cbe11 100644 --- a/packages/checkbox/vite.config.ts +++ b/packages/checkbox/vite.config.ts @@ -1,3 +1,3 @@ -import { viteConfig } from "../../vite.config"; +import { viteConfig } from "../../configs/vite.config"; export default viteConfig; diff --git a/packages/collection/dev/vite.config.ts b/packages/collection/dev/vite.config.ts index 2f3fe11..6b5d194 100644 --- a/packages/collection/dev/vite.config.ts +++ b/packages/collection/dev/vite.config.ts @@ -1,3 +1,3 @@ -import { viteConfig } from "../../../vite.config"; +import { viteConfig } from "../../../configs/vite.config"; export default viteConfig; diff --git a/packages/collection/jest.config.cjs b/packages/collection/jest.config.cjs index c68d815..992a8b0 100644 --- a/packages/collection/jest.config.cjs +++ b/packages/collection/jest.config.cjs @@ -1,4 +1,4 @@ -const baseJest = require("../../jest.config.cjs"); +const baseJest = require("../../configs/jest.config.cjs"); module.exports = { ...baseJest diff --git a/packages/collection/vite.config.ts b/packages/collection/vite.config.ts index c06c04b..b0cbe11 100644 --- a/packages/collection/vite.config.ts +++ b/packages/collection/vite.config.ts @@ -1,3 +1,3 @@ -import { viteConfig } from "../../vite.config"; +import { viteConfig } from "../../configs/vite.config"; export default viteConfig; diff --git a/packages/dialog/dev/vite.config.ts b/packages/dialog/dev/vite.config.ts index 2f3fe11..6b5d194 100644 --- a/packages/dialog/dev/vite.config.ts +++ b/packages/dialog/dev/vite.config.ts @@ -1,3 +1,3 @@ -import { viteConfig } from "../../../vite.config"; +import { viteConfig } from "../../../configs/vite.config"; export default viteConfig; diff --git a/packages/dialog/jest.config.cjs b/packages/dialog/jest.config.cjs index c68d815..992a8b0 100644 --- a/packages/dialog/jest.config.cjs +++ b/packages/dialog/jest.config.cjs @@ -1,4 +1,4 @@ -const baseJest = require("../../jest.config.cjs"); +const baseJest = require("../../configs/jest.config.cjs"); module.exports = { ...baseJest diff --git a/packages/dialog/tsup.config.ts b/packages/dialog/tsup.config.ts index 9097772..066fc44 100644 --- a/packages/dialog/tsup.config.ts +++ b/packages/dialog/tsup.config.ts @@ -1,3 +1,3 @@ -import defaultConfig from "../../tsup.config"; +import defaultConfig from "../../configs/tsup.config"; export default defaultConfig; diff --git a/packages/dialog/vite.config.ts b/packages/dialog/vite.config.ts index c06c04b..b0cbe11 100644 --- a/packages/dialog/vite.config.ts +++ b/packages/dialog/vite.config.ts @@ -1,3 +1,3 @@ -import { viteConfig } from "../../vite.config"; +import { viteConfig } from "../../configs/vite.config"; export default viteConfig; diff --git a/packages/focus/dev/vite.config.ts b/packages/focus/dev/vite.config.ts index 2f3fe11..6b5d194 100644 --- a/packages/focus/dev/vite.config.ts +++ b/packages/focus/dev/vite.config.ts @@ -1,3 +1,3 @@ -import { viteConfig } from "../../../vite.config"; +import { viteConfig } from "../../../configs/vite.config"; export default viteConfig; diff --git a/packages/focus/jest.config.cjs b/packages/focus/jest.config.cjs index c68d815..992a8b0 100644 --- a/packages/focus/jest.config.cjs +++ b/packages/focus/jest.config.cjs @@ -1,4 +1,4 @@ -const baseJest = require("../../jest.config.cjs"); +const baseJest = require("../../configs/jest.config.cjs"); module.exports = { ...baseJest diff --git a/packages/focus/vite.config.ts b/packages/focus/vite.config.ts index c06c04b..b0cbe11 100644 --- a/packages/focus/vite.config.ts +++ b/packages/focus/vite.config.ts @@ -1,3 +1,3 @@ -import { viteConfig } from "../../vite.config"; +import { viteConfig } from "../../configs/vite.config"; export default viteConfig; diff --git a/packages/i18n/dev/vite.config.ts b/packages/i18n/dev/vite.config.ts index 2f3fe11..6b5d194 100644 --- a/packages/i18n/dev/vite.config.ts +++ b/packages/i18n/dev/vite.config.ts @@ -1,3 +1,3 @@ -import { viteConfig } from "../../../vite.config"; +import { viteConfig } from "../../../configs/vite.config"; export default viteConfig; diff --git a/packages/i18n/jest.config.cjs b/packages/i18n/jest.config.cjs index c68d815..992a8b0 100644 --- a/packages/i18n/jest.config.cjs +++ b/packages/i18n/jest.config.cjs @@ -1,4 +1,4 @@ -const baseJest = require("../../jest.config.cjs"); +const baseJest = require("../../configs/jest.config.cjs"); module.exports = { ...baseJest diff --git a/packages/i18n/tsup.config.ts b/packages/i18n/tsup.config.ts index 9097772..066fc44 100644 --- a/packages/i18n/tsup.config.ts +++ b/packages/i18n/tsup.config.ts @@ -1,3 +1,3 @@ -import defaultConfig from "../../tsup.config"; +import defaultConfig from "../../configs/tsup.config"; export default defaultConfig; diff --git a/packages/i18n/vite.config.ts b/packages/i18n/vite.config.ts index c06c04b..b0cbe11 100644 --- a/packages/i18n/vite.config.ts +++ b/packages/i18n/vite.config.ts @@ -1,3 +1,3 @@ -import { viteConfig } from "../../vite.config"; +import { viteConfig } from "../../configs/vite.config"; export default viteConfig; diff --git a/packages/interactions/dev/vite.config.ts b/packages/interactions/dev/vite.config.ts index 2f3fe11..6b5d194 100644 --- a/packages/interactions/dev/vite.config.ts +++ b/packages/interactions/dev/vite.config.ts @@ -1,3 +1,3 @@ -import { viteConfig } from "../../../vite.config"; +import { viteConfig } from "../../../configs/vite.config"; export default viteConfig; diff --git a/packages/interactions/jest.config.cjs b/packages/interactions/jest.config.cjs index c68d815..992a8b0 100644 --- a/packages/interactions/jest.config.cjs +++ b/packages/interactions/jest.config.cjs @@ -1,4 +1,4 @@ -const baseJest = require("../../jest.config.cjs"); +const baseJest = require("../../configs/jest.config.cjs"); module.exports = { ...baseJest diff --git a/packages/interactions/tsup.config.ts b/packages/interactions/tsup.config.ts index 9097772..066fc44 100644 --- a/packages/interactions/tsup.config.ts +++ b/packages/interactions/tsup.config.ts @@ -1,3 +1,3 @@ -import defaultConfig from "../../tsup.config"; +import defaultConfig from "../../configs/tsup.config"; export default defaultConfig; diff --git a/packages/interactions/vite.config.ts b/packages/interactions/vite.config.ts index c06c04b..b0cbe11 100644 --- a/packages/interactions/vite.config.ts +++ b/packages/interactions/vite.config.ts @@ -1,3 +1,3 @@ -import { viteConfig } from "../../vite.config"; +import { viteConfig } from "../../configs/vite.config"; export default viteConfig; diff --git a/packages/label/dev/vite.config.ts b/packages/label/dev/vite.config.ts index 2f3fe11..6b5d194 100644 --- a/packages/label/dev/vite.config.ts +++ b/packages/label/dev/vite.config.ts @@ -1,3 +1,3 @@ -import { viteConfig } from "../../../vite.config"; +import { viteConfig } from "../../../configs/vite.config"; export default viteConfig; diff --git a/packages/label/jest.config.cjs b/packages/label/jest.config.cjs index c68d815..992a8b0 100644 --- a/packages/label/jest.config.cjs +++ b/packages/label/jest.config.cjs @@ -1,4 +1,4 @@ -const baseJest = require("../../jest.config.cjs"); +const baseJest = require("../../configs/jest.config.cjs"); module.exports = { ...baseJest diff --git a/packages/label/jest.config.ssr.cjs b/packages/label/jest.config.ssr.cjs new file mode 100644 index 0000000..0625c04 --- /dev/null +++ b/packages/label/jest.config.ssr.cjs @@ -0,0 +1,5 @@ +const baseJest = require("../../configs/jest.config.ssr.cjs"); + +module.exports = { + ...baseJest +}; diff --git a/packages/label/package.json b/packages/label/package.json index b6acb96..eaa2832 100644 --- a/packages/label/package.json +++ b/packages/label/package.json @@ -34,8 +34,9 @@ "build": "tsup", "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist", "dev": "vite serve dev --host", - "test": "jest --passWithNoTests", - "test:watch": "jest --watch --passWithNoTests", + "test": "pnpm run test:client && pnpm run test:ssr", + "test:client": "jest --passWithNoTests", + "test:ssr": "jest --passWithNoTests --config jest.config.ssr.cjs", "typecheck": "tsc --noEmit" }, "dependencies": { diff --git a/packages/label/test/ssr/createField.ssr.test.tsx b/packages/label/test/ssr/createField.ssr.test.tsx new file mode 100644 index 0000000..5717df3 --- /dev/null +++ b/packages/label/test/ssr/createField.ssr.test.tsx @@ -0,0 +1,77 @@ +/* + * Copyright 2022 Solid Aria Working Group. + * MIT License + * + * Portions of this file are based on code from react-spectrum. + * Copyright 2020 Adobe. All rights reserved. + * + * This file is licensed to you 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 REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { ValidationState } from "@solid-aria/types"; +import { Show } from "solid-js"; +import { renderToString } from "solid-js/web"; + +import { createField } from "../../src"; + +interface TextFieldProps { + label: string; + value?: string; + description?: string; + errorMessage?: string; + validationState?: ValidationState; +} + +const TextInputField = (props: TextFieldProps) => { + let ref: HTMLInputElement | undefined; + + const { labelProps, fieldProps, descriptionProps, errorMessageProps } = createField(props); + + return ( +
+ + +
{props.description}
+ +
{props.errorMessage}
+
+
+ ); +}; + +describe("createField", () => { + it("should return label props", () => { + // renderToString is important to set "hydrable context" for createUniqueId + renderToString(() => { + const { labelProps, fieldProps } = createField({ label: "Test" }); + + expect(labelProps.id).toBeDefined(); + expect(fieldProps.id).toBeDefined(); + + return ""; + }); + }); + + it("should label both the description and error message at the same time", () => { + const string = renderToString(() => ( + + )); + + expect(string).toBe( + `
I describe the field.
I'm a helpful error for the field.
` + ); + }); +}); diff --git a/packages/label/test/ssr/createLabel.ssr.test.tsx b/packages/label/test/ssr/createLabel.ssr.test.tsx new file mode 100644 index 0000000..da04fa6 --- /dev/null +++ b/packages/label/test/ssr/createLabel.ssr.test.tsx @@ -0,0 +1,115 @@ +/* + * Copyright 2022 Solid Aria Working Group. + * MIT License + * + * Portions of this file are based on code from react-spectrum. + * Copyright 2020 Adobe. All rights reserved. + * + * This file is licensed to you 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 REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { renderToString } from "solid-js/web"; + +import { createLabel } from "../../src"; + +describe("createLabel", () => { + it("should return props for visible label", () => { + renderToString(() => { + const { labelProps, fieldProps } = createLabel({ label: "Test" }); + + expect(labelProps.id).toBeDefined(); + expect(fieldProps.id).toBeDefined(); + expect(labelProps.id).toBe(fieldProps["aria-labelledby"]); + expect(labelProps.for).toBe(fieldProps.id); + + // check that generated ids are unique + expect(labelProps.id).not.toBe(fieldProps.id); + + return ""; + }); + }); + + it("should combine aria-labelledby if visible label is also provided", () => { + renderToString(() => { + const { labelProps, fieldProps } = createLabel({ label: "Test", "aria-labelledby": "foo" }); + + expect(labelProps.id).toBeDefined(); + expect(fieldProps.id).toBeDefined(); + expect(fieldProps["aria-labelledby"]).toBe(`foo ${labelProps.id}`); + expect(labelProps.for).toBe(fieldProps.id); + expect(labelProps.id).not.toBe(fieldProps.id); + + return ""; + }); + }); + + it("should combine aria-labelledby if visible label and aria-label is also provided", () => { + renderToString(() => { + const { labelProps, fieldProps } = createLabel({ + label: "Test", + "aria-labelledby": "foo", + "aria-label": "bar" + }); + + expect(labelProps.id).toBeDefined(); + expect(fieldProps.id).toBeDefined(); + expect(fieldProps["aria-label"]).toBe("bar"); + expect(fieldProps["aria-labelledby"]).toBe(`foo ${labelProps.id} ${fieldProps.id}`); + expect(labelProps.for).toBe(fieldProps.id); + expect(labelProps.id).not.toBe(fieldProps.id); + + return ""; + }); + }); + + it("should work without a visible label", () => { + renderToString(() => { + const { labelProps, fieldProps } = createLabel({ "aria-label": "Label" }); + + expect(labelProps.id).toBeUndefined(); + expect(labelProps.for).toBeUndefined(); + expect(fieldProps.id).toBeDefined(); + expect(fieldProps["aria-labelledby"]).toBeUndefined(); + expect(fieldProps["aria-label"]).toBe("Label"); + + return ""; + }); + }); + + it("should work without a visible label and both aria-label and aria-labelledby", async () => { + renderToString(() => { + const { labelProps, fieldProps } = createLabel({ + "aria-label": "Label", + "aria-labelledby": "foo" + }); + + expect(labelProps.id).toBeUndefined(); + expect(labelProps.for).toBeUndefined(); + expect(fieldProps.id).toBeDefined(); + expect(fieldProps["aria-labelledby"]).toBe(`foo ${fieldProps.id}`); + expect(fieldProps["aria-label"]).toBe("Label"); + + return ""; + }); + }); + + it("should not return a `for` attribute when the label element is not