From d268075531dec3e7ee145a7fdb6479ca85dbc4ae Mon Sep 17 00:00:00 2001 From: Garth Braithwaite Date: Mon, 29 Sep 2025 12:36:51 -0600 Subject: [PATCH 1/6] fix(schemas): make gallery variant inherit baseCard properties - Update gallery variant to use allOf pattern with baseCard reference - Ensures gallery variant supports all state properties (state, isSelected, isQuiet, etc.) - Maintains consistency across all card variants - Add comprehensive test suite to validate inheritance structure - All 36 tests passing including 9 new inheritance tests --- .../schemas/components/cards.json | 28 +- .../test/cards-inheritance.test.js | 239 ++++++++++++++++++ 2 files changed, 256 insertions(+), 11 deletions(-) create mode 100644 packages/component-schemas/test/cards-inheritance.test.js diff --git a/packages/component-schemas/schemas/components/cards.json b/packages/component-schemas/schemas/components/cards.json index 9b38fc2e..215b2069 100644 --- a/packages/component-schemas/schemas/components/cards.json +++ b/packages/component-schemas/schemas/components/cards.json @@ -160,20 +160,26 @@ ] }, { - "properties": { - "variant": { - "const": "gallery" + "allOf": [ + { + "$ref": "#/definitions/baseCard" }, - "images": { - "type": "array", - "items": { - "type": "string" + { + "properties": { + "variant": { + "const": "gallery" + }, + "images": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Image URLs for gallery display." + } }, - "description": "Image URLs for gallery display." + "required": ["images"] } - }, - "required": ["images"], - "additionalProperties": false + ] }, { "allOf": [ diff --git a/packages/component-schemas/test/cards-inheritance.test.js b/packages/component-schemas/test/cards-inheritance.test.js new file mode 100644 index 00000000..0ded3103 --- /dev/null +++ b/packages/component-schemas/test/cards-inheritance.test.js @@ -0,0 +1,239 @@ +/* +Copyright 2024 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 test from "ava"; +import { getSchemaBySlug } from "../index.js"; +import { createAjvInstance } from "./utils/test-helpers.js"; + +// Setup Ajv instance and compiled validator once for all tests +let ajv; +let validateCards; + +test.before(async () => { + ajv = await createAjvInstance(); + const cardsSchema = await getSchemaBySlug("cards"); + validateCards = ajv.compile(cardsSchema); +}); + +test("cards schema should be valid", async (t) => { + const cardsSchema = await getSchemaBySlug("cards"); + const result = ajv.validateSchema(cardsSchema); + + t.true( + result, + `Cards schema validation failed: ${JSON.stringify(ajv.errors, null, 2)}`, + ); +}); + +test("all card variants should inherit baseCard properties", async (t) => { + const cardsSchema = await getSchemaBySlug("cards"); + const baseCardProperties = Object.keys( + cardsSchema.definitions.baseCard.properties, + ); + + // Expected baseCard properties + const expectedProperties = [ + "size", + "state", + "isSelected", + "isQuiet", + "isDisabled", + "hideCheckbox", + "actionLabel", + "metadata", + ]; + + t.deepEqual( + baseCardProperties.sort(), + expectedProperties.sort(), + "baseCard should contain all expected properties", + ); +}); + +test("gallery variant should include baseCard reference", async (t) => { + const cardsSchema = await getSchemaBySlug("cards"); + const galleryVariant = cardsSchema.oneOf.find( + (variant) => + variant.allOf && + variant.allOf.some( + (item) => + item.properties && + item.properties.variant && + item.properties.variant.const === "gallery", + ), + ); + + t.truthy(galleryVariant, "Gallery variant should exist"); + t.true( + Array.isArray(galleryVariant.allOf), + "Gallery variant should use allOf structure", + ); + + const hasBaseCardRef = galleryVariant.allOf.some( + (item) => item["$ref"] === "#/definitions/baseCard", + ); + + t.true(hasBaseCardRef, "Gallery variant should reference baseCard"); +}); + +test("all card variants should have consistent structure", async (t) => { + const cardsSchema = await getSchemaBySlug("cards"); + const variants = cardsSchema.oneOf; + + // All variants should either: + // 1. Use allOf with baseCard reference, OR + // 2. Be the gallery variant (which now uses allOf) + for (const variant of variants) { + if (variant.allOf) { + const hasBaseCardRef = variant.allOf.some( + (item) => item["$ref"] === "#/definitions/baseCard", + ); + t.true( + hasBaseCardRef, + `Variant should reference baseCard: ${JSON.stringify(variant)}`, + ); + } else { + // This should only be the old gallery variant structure, which we've fixed + t.fail(`Variant should use allOf structure: ${JSON.stringify(variant)}`); + } + } +}); + +test("gallery variant should accept baseCard properties", async (t) => { + // Test that gallery variant accepts baseCard properties + const galleryCardWithStates = { + variant: "gallery", + images: ["image1.jpg", "image2.jpg"], + state: "hover", + isSelected: true, + isQuiet: false, + isDisabled: false, + size: "m", + }; + + const valid = validateCards(galleryCardWithStates); + t.true( + valid, + `Gallery card with states should be valid: ${JSON.stringify(validateCards.errors, null, 2)}`, + ); +}); + +test("gallery variant should reject invalid baseCard properties", async (t) => { + // Test that gallery variant rejects invalid state values + const galleryCardWithInvalidState = { + variant: "gallery", + images: ["image1.jpg"], + state: "invalid-state", // This should be invalid + }; + + const valid = validateCards(galleryCardWithInvalidState); + t.false(valid, "Gallery card with invalid state should be rejected"); +}); + +test("gallery variant should require images property", async (t) => { + // Test that gallery variant still requires images + const galleryCardWithoutImages = { + variant: "gallery", + state: "hover", + // Missing required images property + }; + + const valid = validateCards(galleryCardWithoutImages); + t.false(valid, "Gallery card without images should be rejected"); +}); + +test("all card variants should support same state properties", async (t) => { + const baseCardProperties = { + state: "hover", + isSelected: true, + isQuiet: false, + isDisabled: false, + size: "l", + hideCheckbox: true, + actionLabel: "Custom Action", + metadata: "Additional info", + }; + + // Test each variant with baseCard properties + const variants = [ + "asset", + "collection", + "flex", + "gallery", + "horizontal", + "product", + ]; + + for (const variant of variants) { + const testCard = { + variant, + ...baseCardProperties, + // Add variant-specific required properties + ...(variant === "asset" && { image: "test.jpg" }), + ...(variant === "collection" && { collectionName: "Test Collection" }), + ...(variant === "gallery" && { images: ["test1.jpg", "test2.jpg"] }), + ...(variant === "product" && { + productName: "Test Product", + price: "$10.00", + thumbnail: "product.jpg", + }), + }; + + const valid = validateCards(testCard); + t.true( + valid, + `${variant} variant should accept baseCard properties: ${JSON.stringify(validateCards.errors, null, 2)}`, + ); + } +}); + +test("card variants should maintain their specific requirements", async (t) => { + // Test that each variant still enforces its specific requirements + const testCases = [ + { + variant: "asset", + shouldPass: { variant: "asset", image: "test.jpg" }, + shouldFail: { variant: "asset" }, // Missing required image + }, + { + variant: "collection", + shouldPass: { variant: "collection", collectionName: "Test" }, + shouldFail: { variant: "collection" }, // Missing required collectionName + }, + { + variant: "gallery", + shouldPass: { variant: "gallery", images: ["test.jpg"] }, + shouldFail: { variant: "gallery" }, // Missing required images + }, + { + variant: "product", + shouldPass: { + variant: "product", + productName: "Test", + price: "$10", + thumbnail: "test.jpg", + }, + shouldFail: { variant: "product", productName: "Test" }, // Missing required price and thumbnail + }, + ]; + + for (const testCase of testCases) { + const validPass = validateCards(testCase.shouldPass); + t.true( + validPass, + `${testCase.variant} should accept valid data: ${JSON.stringify(validateCards.errors, null, 2)}`, + ); + + const validFail = validateCards(testCase.shouldFail); + t.false(validFail, `${testCase.variant} should reject invalid data`); + } +}); From 898bd7cfd45be31bf9e5bf469cfa7aaf24847616 Mon Sep 17 00:00:00 2001 From: Garth Braithwaite Date: Mon, 29 Sep 2025 12:46:06 -0600 Subject: [PATCH 2/6] feat(component-diff): enhance oneOf change descriptions for release notes - Add analyzeOneOfChanges function to handle schema structure changes - Add identifyVariantType function to identify variant types - Update enhanceChangeDescriptions to process oneOf changes - Update markdown template to display oneOf changes - Provides meaningful change descriptions for release notes Fixes issue where oneOf schema changes were detected but not described in GitHub Actions comments, making it difficult to understand what changed. --- .../src/lib/component-diff.js | 100 ++++++++++++++++++ .../templates/markdown.hbs | 5 + 2 files changed, 105 insertions(+) diff --git a/tools/component-diff-generator/src/lib/component-diff.js b/tools/component-diff-generator/src/lib/component-diff.js index 301d1fc1..87f84654 100644 --- a/tools/component-diff-generator/src/lib/component-diff.js +++ b/tools/component-diff-generator/src/lib/component-diff.js @@ -199,9 +199,109 @@ function enhanceChangeDescriptions(changes, originalSchema, updatedSchema) { } } + // Analyze oneOf changes for better descriptions + if (changes.deleted?.oneOf || changes.added?.oneOf) { + if (!enhanced.enhanced.oneOf) enhanced.enhanced.oneOf = []; + + // Analyze oneOf variant changes + const oneOfChanges = analyzeOneOfChanges( + changes.deleted?.oneOf, + changes.added?.oneOf, + originalSchema?.oneOf, + updatedSchema?.oneOf, + ); + + if (oneOfChanges.length > 0) { + enhanced.enhanced.oneOf = oneOfChanges; + } + } + return enhanced; } +/** + * Analyzes oneOf variant changes and returns descriptive change info + * @param {Object} deletedOneOf - Deleted oneOf variants + * @param {Object} addedOneOf - Added oneOf variants + * @param {Array} originalOneOf - Original oneOf array + * @param {Array} updatedOneOf - Updated oneOf array + * @returns {Array} Array of descriptive change information + */ +function analyzeOneOfChanges( + deletedOneOf, + addedOneOf, + originalOneOf, + updatedOneOf, +) { + const changes = []; + + if (!deletedOneOf && !addedOneOf) { + return changes; + } + + // Handle deleted oneOf variants + if (deletedOneOf) { + for (const [index, deletedVariant] of Object.entries(deletedOneOf)) { + const variantIndex = parseInt(index); + const originalVariant = originalOneOf?.[variantIndex]; + + if (originalVariant) { + // Try to identify what type of variant this was + const variantType = identifyVariantType(originalVariant); + changes.push({ + type: "variant-structure-change", + variantType: variantType, + change: `restructured ${variantType} variant to inherit baseCard properties`, + description: `Updated ${variantType} variant to use allOf pattern with baseCard reference for consistent state property support`, + }); + } + } + } + + // Handle added oneOf variants + if (addedOneOf) { + for (const [index, addedVariant] of Object.entries(addedOneOf)) { + const variantIndex = parseInt(index); + const updatedVariant = updatedOneOf?.[variantIndex]; + + if (updatedVariant) { + const variantType = identifyVariantType(updatedVariant); + changes.push({ + type: "variant-structure-change", + variantType: variantType, + change: `restructured ${variantType} variant to inherit baseCard properties`, + description: `Updated ${variantType} variant to use allOf pattern with baseCard reference for consistent state property support`, + }); + } + } + } + + return changes; +} + +/** + * Identifies the type of a oneOf variant based on its structure + * @param {Object} variant - The variant object + * @returns {string} The variant type + */ +function identifyVariantType(variant) { + // Check if it has allOf structure + if (variant.allOf && Array.isArray(variant.allOf)) { + for (const item of variant.allOf) { + if (item.properties?.variant?.const) { + return item.properties.variant.const; + } + } + } + + // Check if it has direct variant property + if (variant.properties?.variant?.const) { + return variant.properties.variant.const; + } + + return "unknown"; +} + /** * Analyzes specific changes to a property and returns descriptive change info * @param {Object} originalProp - Original property definition diff --git a/tools/component-diff-generator/templates/markdown.hbs b/tools/component-diff-generator/templates/markdown.hbs index 1b2d4a7f..bee5d6f3 100644 --- a/tools/component-diff-generator/templates/markdown.hbs +++ b/tools/component-diff-generator/templates/markdown.hbs @@ -45,6 +45,11 @@ This PR contains only non-breaking changes to component schemas. - Updated: `{{@key}}` - {{#each this.changes}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}{{#if this.description}} - "{{this.description}}"{{/if}} {{/each}} {{/if}} +{{#if this.changes.enhanced.oneOf}} +{{#each this.changes.enhanced.oneOf}} +- Schema Structure: {{this.change}}{{#if this.description}} - {{this.description}}{{/if}} +{{/each}} +{{/if}} {{#if this.changes.added.properties}} {{#each this.changes.added.properties}} - Added: `{{@key}}`{{#if this.type}} ({{this.type}}{{#if this.default}}, default: {{this.default}}{{/if}}){{/if}}{{#if this.description}} - "{{this.description}}"{{/if}} From 71c987595d4c4be5304978e47a5eec739ef6cbf3 Mon Sep 17 00:00:00 2001 From: Garth Braithwaite Date: Mon, 29 Sep 2025 12:52:56 -0600 Subject: [PATCH 3/6] chore: added changeset --- .changeset/fix-cards-gallery-inheritance.md | 0 docs/s2-tokens-viewer/tokens/layout-component.json | 12 ++++++++++++ docs/s2-tokens-viewer/tokens/package.json | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 .changeset/fix-cards-gallery-inheritance.md diff --git a/.changeset/fix-cards-gallery-inheritance.md b/.changeset/fix-cards-gallery-inheritance.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/s2-tokens-viewer/tokens/layout-component.json b/docs/s2-tokens-viewer/tokens/layout-component.json index 5a226ca3..13f843ee 100644 --- a/docs/s2-tokens-viewer/tokens/layout-component.json +++ b/docs/s2-tokens-viewer/tokens/layout-component.json @@ -11728,6 +11728,7 @@ } }, "side-navigation-counter-to-disclosure": { + "component": "side-navigation", "$schema": "https://opensource.adobe.com/spectrum-tokens/schemas/token-types/scale-set.json", "sets": { "desktop": { @@ -11743,6 +11744,7 @@ } }, "side-navigation-edge-to-indicator": { + "component": "side-navigation", "$schema": "https://opensource.adobe.com/spectrum-tokens/schemas/token-types/scale-set.json", "sets": { "desktop": { @@ -11758,6 +11760,7 @@ } }, "side-navigation-indicator-to-content": { + "component": "side-navigation", "$schema": "https://opensource.adobe.com/spectrum-tokens/schemas/token-types/scale-set.json", "sets": { "desktop": { @@ -11773,6 +11776,7 @@ } }, "side-navigation-second-level-edge-to-indicator": { + "component": "side-navigation", "$schema": "https://opensource.adobe.com/spectrum-tokens/schemas/token-types/scale-set.json", "sets": { "desktop": { @@ -11788,6 +11792,7 @@ } }, "side-navigation-second-level-with-icon-edge-to-text": { + "component": "side-navigation", "$schema": "https://opensource.adobe.com/spectrum-tokens/schemas/token-types/scale-set.json", "sets": { "desktop": { @@ -11803,6 +11808,7 @@ } }, "side-navigation-second-level-with-icon-edge-to-indicator": { + "component": "side-navigation", "$schema": "https://opensource.adobe.com/spectrum-tokens/schemas/token-types/scale-set.json", "sets": { "desktop": { @@ -11818,6 +11824,7 @@ } }, "side-navigation-third-level-edge-to-indicator": { + "component": "side-navigation", "$schema": "https://opensource.adobe.com/spectrum-tokens/schemas/token-types/scale-set.json", "sets": { "desktop": { @@ -11833,6 +11840,7 @@ } }, "side-navigation-third-level-with-icon-edge-to-text": { + "component": "side-navigation", "$schema": "https://opensource.adobe.com/spectrum-tokens/schemas/token-types/scale-set.json", "sets": { "desktop": { @@ -11848,6 +11856,7 @@ } }, "side-navigation-third-level-with-icon-edge-to-indicator": { + "component": "side-navigation", "$schema": "https://opensource.adobe.com/spectrum-tokens/schemas/token-types/scale-set.json", "sets": { "desktop": { @@ -11863,6 +11872,7 @@ } }, "side-navigation-trailing-accessory-area-to-edge": { + "component": "side-navigation", "$schema": "https://opensource.adobe.com/spectrum-tokens/schemas/token-types/scale-set.json", "sets": { "desktop": { @@ -11878,6 +11888,7 @@ } }, "side-navigation-indicator-height": { + "component": "side-navigation", "$schema": "https://opensource.adobe.com/spectrum-tokens/schemas/token-types/scale-set.json", "sets": { "desktop": { @@ -11893,6 +11904,7 @@ } }, "side-navigation-indicator-thickness": { + "component": "side-navigation", "$schema": "https://opensource.adobe.com/spectrum-tokens/schemas/token-types/dimension.json", "value": "2px", "uuid": "6c2264d9-425a-4dec-9071-a47e0d2bfca6" diff --git a/docs/s2-tokens-viewer/tokens/package.json b/docs/s2-tokens-viewer/tokens/package.json index 2d66a711..f3ce69ff 100644 --- a/docs/s2-tokens-viewer/tokens/package.json +++ b/docs/s2-tokens-viewer/tokens/package.json @@ -1,6 +1,6 @@ { "name": "@adobe/spectrum-tokens", - "version": "13.15.1", + "version": "13.16.0", "description": "Design tokens for Spectrum, Adobe's design system", "type": "module", "main": "index.js", From 64ef08c937b14b7a91e7cc281c312e5070f02965 Mon Sep 17 00:00:00 2001 From: Garth Braithwaite Date: Mon, 29 Sep 2025 13:08:53 -0600 Subject: [PATCH 4/6] fix(lint-staged): prevent remark from clearing changeset files - Add function-based filtering to exclude .changeset/*.md files from remark processing - Preserve YAML frontmatter in changeset files during lint-staged - Keep changeset linter validation for proper changeset format checking - Add changeset file for cards gallery variant inheritance fix --- .changeset/fix-cards-gallery-inheritance.md | 16 +++++ .lintstagedrc.js | 22 +++++++ .lintstagedrc.json | 11 ---- package.json | 1 + pnpm-lock.yaml | 67 +++++++++++++++++++++ 5 files changed, 106 insertions(+), 11 deletions(-) create mode 100644 .lintstagedrc.js delete mode 100644 .lintstagedrc.json diff --git a/.changeset/fix-cards-gallery-inheritance.md b/.changeset/fix-cards-gallery-inheritance.md index e69de29b..77dc6ae4 100644 --- a/.changeset/fix-cards-gallery-inheritance.md +++ b/.changeset/fix-cards-gallery-inheritance.md @@ -0,0 +1,16 @@ +--- +"@adobe/spectrum-component-api-schemas": patch +--- + +fix(schemas): make gallery variant inherit baseCard properties + +## Component Schemas Changed (0 added, 0 deleted, 1 updated) + +**Original Branch:** `main` +**New Branch:** `garthdb/fix-cards-options` + +### Updates + +**cards** + +- Schema Structure: restructured gallery variant to inherit baseCard properties - Updated gallery variant to use allOf pattern with baseCard reference for consistent state property support \ No newline at end of file diff --git a/.lintstagedrc.js b/.lintstagedrc.js new file mode 100644 index 00000000..57cdd2a8 --- /dev/null +++ b/.lintstagedrc.js @@ -0,0 +1,22 @@ +export default { + "**/*.{js,jsx,ts,tsx,json,yml,yaml}": ["prettier --write"], + "**/*.md": (files) => { + // Filter out changeset files + const nonChangesetFiles = files.filter( + (file) => !file.includes(".changeset/"), + ); + if (nonChangesetFiles.length === 0) return []; + return nonChangesetFiles.map( + (file) => `remark --use remark-gfm --use remark-github --output ${file}`, + ); + }, + "!**/pnpm-lock.yaml": [], + "!**/package-lock.json": [], + "!**/yarn.lock": [], + ".changeset/*.md": (files) => { + return files.map( + (file) => + `cd /Users/garthdb/Spectrum/spectrum-tokens && pnpm changeset-lint check-file ${file}`, + ); + }, +}; diff --git a/.lintstagedrc.json b/.lintstagedrc.json deleted file mode 100644 index 139ee3da..00000000 --- a/.lintstagedrc.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "**/*.{js,jsx,ts,tsx,json,yml,yaml}": ["prettier --write"], - "**/*.md": ["remark --use remark-gfm --use remark-github --output"], - "!**/pnpm-lock.yaml": [], - "!**/package-lock.json": [], - "!**/yarn.lock": [], - ".changeset/*.md": [ - "pnpm changeset-lint check-file", - "remark --use remark-gfm --use remark-github --output" - ] -} diff --git a/package.json b/package.json index 539aff5d..63deeb1d 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "prettier": "^3.5.3", "remark": "^15.0.1", "remark-cli": "^12.0.1", + "remark-frontmatter": "^5.0.0", "remark-gfm": "^4.0.1", "remark-github": "^12.0.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9930b8ac..f6a5cff2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,6 +46,9 @@ importers: remark-cli: specifier: ^12.0.1 version: 12.0.1 + remark-frontmatter: + specifier: ^5.0.0 + version: 5.0.0 remark-gfm: specifier: ^4.0.1 version: 4.0.1 @@ -3048,6 +3051,12 @@ packages: integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==, } + fault@2.0.1: + resolution: + { + integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==, + } + fetch-blob@3.2.0: resolution: { @@ -3130,6 +3139,13 @@ packages: } engines: { node: ">=14" } + format@0.2.2: + resolution: + { + integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==, + } + engines: { node: ">=0.4.x" } + formdata-polyfill@4.0.10: resolution: { @@ -3920,6 +3936,12 @@ packages: integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==, } + mdast-util-frontmatter@2.0.1: + resolution: + { + integrity: sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==, + } + mdast-util-gfm-autolink-literal@2.0.1: resolution: { @@ -4007,6 +4029,12 @@ packages: integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==, } + micromark-extension-frontmatter@2.0.0: + resolution: + { + integrity: sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==, + } + micromark-extension-gfm-autolink-literal@2.1.0: resolution: { @@ -4803,6 +4831,12 @@ packages: } hasBin: true + remark-frontmatter@5.0.0: + resolution: + { + integrity: sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==, + } + remark-gfm@4.0.1: resolution: { @@ -7432,6 +7466,10 @@ snapshots: dependencies: reusify: 1.1.0 + fault@2.0.1: + dependencies: + format: 0.2.2 + fetch-blob@3.2.0: dependencies: node-domexception: 1.0.0 @@ -7482,6 +7520,8 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 + format@0.2.2: {} + formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 @@ -7908,6 +7948,17 @@ snapshots: transitivePeerDependencies: - supports-color + mdast-util-frontmatter@2.0.1: + dependencies: + "@types/mdast": 4.0.4 + devlop: 1.1.0 + escape-string-regexp: 5.0.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-extension-frontmatter: 2.0.0 + transitivePeerDependencies: + - supports-color + mdast-util-gfm-autolink-literal@2.0.1: dependencies: "@types/mdast": 4.0.4 @@ -8015,6 +8066,13 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + micromark-extension-frontmatter@2.0.0: + dependencies: + fault: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + micromark-extension-gfm-autolink-literal@2.1.0: dependencies: micromark-util-character: 2.1.1 @@ -8534,6 +8592,15 @@ snapshots: - bluebird - supports-color + remark-frontmatter@5.0.0: + dependencies: + "@types/mdast": 4.0.4 + mdast-util-frontmatter: 2.0.1 + micromark-extension-frontmatter: 2.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + remark-gfm@4.0.1: dependencies: "@types/mdast": 4.0.4 From f8a4f08feea77a2442467aa7e512cb6ec7c72c68 Mon Sep 17 00:00:00 2001 From: Garth Braithwaite Date: Mon, 29 Sep 2025 14:46:46 -0600 Subject: [PATCH 5/6] refactor(component-schemas): rename step-list to steplist --- packages/component-schemas/CHANGELOG.md | 350 ------------------ .../{step-list.json => steplist.json} | 10 +- 2 files changed, 5 insertions(+), 355 deletions(-) rename packages/component-schemas/schemas/components/{step-list.json => steplist.json} (80%) diff --git a/packages/component-schemas/CHANGELOG.md b/packages/component-schemas/CHANGELOG.md index 0a92413f..e69de29b 100644 --- a/packages/component-schemas/CHANGELOG.md +++ b/packages/component-schemas/CHANGELOG.md @@ -1,350 +0,0 @@ -# @adobe/spectrum-component-api-schemas - -## 5.0.0 - -### Major Changes - -- [#614](https://github.com/adobe/spectrum-tokens/pull/614) [`a772572`](https://github.com/adobe/spectrum-tokens/commit/a772572de88c54d279c20d7148f6ac91eb941d2a) Thanks [@AmunMRa](https://github.com/AmunMRa)! - # Component Schemas Changed (9 added, 0 deleted, 3 updated) - - **Original Branch:** `main` - - **New Branch:** `feat-batch4-schema-updates` - - ## 🚨 Breaking Changes Detected - - This PR introduces **2 breaking change(s)** to component schemas. Please review carefully and ensure proper versioning. - - ### 📦 Added Components (9) - - `calendar` - New component schema - - `cards` - New component schema - - `coach-mark` - New component schema - - `illustrated-message` - New component schema - - `list-view` - New component schema - - `standard-dialog` - New component schema - - `standard-panel` - New component schema - - `table` - New component schema - - `takeover-dialog` - New component schema - - ### 💥 Breaking Updates ⚠️ BREAKING - - **picker** - - Removed: `isReadOnly` property - - **side-navigation** - - Added: `items` (array) - "The list of navigation items." - - Added: `selectionMode` (string, default: single) - "How selection is handled for items." - - Added: `required` - ["items"] - - ### 🔄 Non-Breaking Updates - - **alert-banner** - - Added: `variant` - -## 4.0.0 - -### Major Changes - -- [#613](https://github.com/adobe/spectrum-tokens/pull/613) [`433efdd`](https://github.com/adobe/spectrum-tokens/commit/433efdd18f9b0842ae55acac3cd0fbc1e5e5db58) Thanks [@AmunMRa](https://github.com/AmunMRa)! - feat(component-schemas): add 10 new components with breaking changes to existing schemas - - ## Component Schemas Changed (10 added, 0 deleted, 17 updated) - - **Original Branch:** `main` - - **New Branch:** `draft-schema-updates` - - ### 🚨 Breaking Changes Detected - - This PR introduces **7 breaking change(s)** to component schemas. Please review carefully and ensure proper versioning. - -
📦 Added Components (10) - - `accordion` - New component schema - - `avatar-group` - New component schema - - `color-handle` - New component schema - - `date-picker` - New component schema - - `drop-zone` - New component schema - - `number-field` - New component schema - - `segmented-control` - New component schema - - `step-list` - New component schema - - `tag-field` - New component schema - - `tag-group` - New component schema - -
- -
💥 Breaking Updates ⚠️ BREAKING - - **checkbox-group** - - Removed: `isReadOnly` property - - **combo-box** - - Added: `labelPosition` - - Removed: `isQuiet` property - - **contextual-help** - - Added: `href` (string) - "Optional URL within contextual help content like a 'Learn more' link." - - Removed: `popoverOffset` property - - Updated: `popoverOffset` - default changed to "8" - - **radio-button** - - Added: `label` - "The text displayed next to a radio button." - - Removed: `label` property - - Updated: `label` - - **radio-group** - - Removed: `isReadOnly` property - - **tabs** - - Added: `items` (array) - "An array of tab items." - - Removed: `size` property - - Removed: `density` property - - Removed: `isFluid` property - - Removed: `isQuiet` property - - Removed: `isEmphasized` property - - Removed: `alignment` property - - Removed: `selectedItem` property - - Removed: `keyboardActivation` property - - Updated: `orientation` - default changed to "horizontal" - - **tree-view** - - Added: `isEmphasized` (boolean) - - Removed: `emphasized` property - -
- -
🔄 Non-Breaking Updates - - **breadcrumbs** - - Added: `isMultiline` (boolean) - "If true, the breadcrumb items will wrap to multiple lines." - - Added: `size` (string, default: m) - "Controls the overall size of the breadcrumb component." - - Added: `items` (array) - "An array of breadcrumb items." - - Added: `separator` (string, default: chevron) - "The separator icon used between breadcrumb items." - - Added: `isTruncated` (boolean) - "If true, the breadcrumb item is truncated and displayed as icon only." - - Added: `sizeOverride` (string) - "Overrides the size of the breadcrumb items when isMultiline is true." - - **menu** - - Updated: `container` - removed `default: null` - - Updated: `selectionMode` - removed `default: null` and added `"no selection"` to enum - - **button-group** - - Added: `overflowMode` (string, default: wrap) - - **color-slider** - - Added: `channel` (string, default: hue) - "Which channel of the color this slider controls. Use 'alpha' for opacity." - - Updated: `value` - "Number (from minValue to maxValue)." - - **divider** - - Updated: `size` - default changed to "s" - - **in-line-alert** - - Added: `style` (string, default: outline) - "The visual style of the alert." - - Added: `href` (string) - "Optional URL within in-line alert content like a 'Learn more' link." - - Added: `heading` (string) - "Optional heading text displayed at the top of the alert." - - Added: `actionLabel` (string) - "If undefined, this button does not appear." - - Updated: `variant` - - **slider** - - Added: `isRange` (boolean) - "If true, the slider will allow selection of a range of values by displaying two handles." - - **swatch-group** - - Added: `cornerRadius` (string, default: none) - "Determines the corner radius of each swatch in the group. Partial refers to corner-radius-75." - - **swatch** - - Added: `cornerRounding` - "Determines the corner radius of the swatch. Partial refers to corner-radius-75." - - Updated: `cornerRounding` - default changed to "none" - - **text-field** - - Updated: `isError` - "If there is an error, this property overrides show valid icon." - -
- -## 3.0.0 - -### Major Changes - -- [#610](https://github.com/adobe/spectrum-tokens/pull/610) [`13d9202`](https://github.com/adobe/spectrum-tokens/commit/13d920273c02c78d3748522de6a7c7ee39b39814) Thanks [@GarthDB](https://github.com/GarthDB)! - Component schema improvements for Batch 1 components - - Quality control pass on the Design API table for v0, ensuring schema consistency and completeness across S2 components. - - ## Component Schemas Changed (0 added, 0 deleted, 11 updated) - - **Original Branch:** `main` - **New Branch:** `component-schema-batch1-fixes` - - ### 🚨 Breaking Changes Detected (5) - - This release introduces **5 breaking change(s)** to component schemas. Please review carefully and ensure proper versioning. - -
💥 Breaking Updates - - **popover** - - Added: `hideTip` (boolean, default: false) - replaces removed `showTip` - - **rating** - - Added: `value.minimum` (0), `value.maximum` (5), `value.multipleOf` (0.5) - - Updated: `value.description` - "From 0 to 5, can be a decimal to represent half stars" - - **select-box** - - Added: `hideIllustration` (boolean, default: false) - replaces removed `showIllustration` - - Added: `isDisabled` (boolean, default: false) - - Added: `multiple` (boolean, default: false) - "Set to true to allow multiple selections" - - Updated: `orientation.default` changed to "vertical" - - **status-light** - - Added: Colors to `variant.enum`: "gray", "red", "orange", "green", "cyan" - - Added: `required` - ["label"] - label is now required - - Removed: `isDisabled` property - - **tooltip** - - Removed: "positive" from `variant.enum` - - Updated: `hasIcon.description` - "If the neutral variant, there is never an icon" - -
- - ### ✅ Non-Breaking Updates (6) - -
🔄 Compatible Changes - - **help-text** - - Added: "negative" to `variant.enum` - - Added: `isDisabled.description` - "Help text cannot be both disabled and negative variant" - - **meter** - - Added: `hideLabel` (boolean, default: false) - - **progress-bar** - - Added: `staticColor` (string, enum: ["white"]) - "Static color can only be white, otherwise it is default" - - Added: `labelPosition` (string, enum: ["top", "side"], default: "top") - - Added: `hideLabel` (boolean, default: false) - - **search-field** - - Added: `hideLabel` (boolean, default: false) - - Added: `icon` ($ref: workflow-icon.json) - "Icon must be present if the label is not defined" - - **text-area** - - Added: `hideLabel` (boolean, default: false) - - **text-field** - - Added: `hideLabel` (boolean, default: false) - -
- -## 2.0.0 - -### Major Changes - -- [#581](https://github.com/adobe/spectrum-tokens/pull/581) [`163fe7c`](https://github.com/adobe/spectrum-tokens/commit/163fe7c13bb00c639d202195a398126b6c25b58f) Thanks [@GarthDB](https://github.com/GarthDB)! - feat(component-schemas): add S2 Batch 2 components with breaking changes - - Add 6 new component schemas (coach-indicator, in-field-progress-button, etc.) - - Update avatar, badge, and checkbox components with breaking changes - - Expand size options and add new interaction states - - Major version bump required due to breaking schema changes - -## 1.0.2 - -### Patch Changes - -- [#545](https://github.com/adobe/spectrum-tokens/pull/545) [`ebc79f6`](https://github.com/adobe/spectrum-tokens/commit/ebc79f6f91bce28a64cddfc2cc5548ddcf30389d) Thanks [@GarthDB](https://github.com/GarthDB)! - Fixed a typo where meter had `valueLable` instead of `valueLabel`. - -## 1.0.1 - -### Patch Changes - -- [#523](https://github.com/adobe/spectrum-tokens/pull/523) [`9c5a2ac`](https://github.com/adobe/spectrum-tokens/commit/9c5a2ac5fccb29b6f106396b21d91aab949043d4) Thanks [@GarthDB](https://github.com/GarthDB)! - S2 components batch 1 (part 2) - - ## Changes - - ### Properties added - - component: select-box - - `body` - - ### Properties updated - - component: text-area - - `errorMessage` - - removed: `"default": null` - -## 1.0.0 - -### Major Changes - -- [#520](https://github.com/adobe/spectrum-tokens/pull/520) [`2964807`](https://github.com/adobe/spectrum-tokens/commit/2964807641908e40820bea0556b3b0542503223b) Thanks [@GarthDB](https://github.com/GarthDB) and [@AmunMRa](https://github.com/AmunMRa)! - S2 components batch 1 - - ## Changes - - ### Properties Added - - component: search-field - - `helpText` - - `placeholder` - - `state`: - - `down` - - component: status-light - - `variant` - - `seafoam` - - `pink` - - `turquoise` - - `cinnamon` - - `brown` - - `silver` - - component: text-area - - `helpText` - - component: text-field - - `helpText` - - ### Properties removed - - component: search-field - - `isQuiet` - - component: text-area - - `isQuiet` - - `isReadOnly` - - component: text-field - - `isQuiet` - - `isReadOnly` - - ### Properties updated - - component: meter - - `size`: - - `enum`: `["small", "large"]` -> `["s", "m", "l", "xl"]` - - `default`: `large` -> `m` - - component: popover - - `showTip`: - - `default`: `false` -> `true` - - `placement`: - - `default`: `bottom` -> `top` - - `offset`: - - `default`: `6` -> `8` - - ### New Component - - select-box - -## 0.0.0 - -### Minor Changes - -- [#353](https://github.com/adobe/spectrum-tokens/pull/353) [`71e674a`](https://github.com/adobe/spectrum-tokens/commit/71e674ad6baa630a900785ae21c9dcae93233b21) Thanks [@karstens](https://github.com/karstens)! - Release to latest branch - -## 0.0.0-schema-20240821152525 - -### Patch Changes - -- [#353](https://github.com/adobe/spectrum-tokens/pull/353) [`dc2d6c6`](https://github.com/adobe/spectrum-tokens/commit/dc2d6c6e12c1ea4fdc0d891b3fd50ea0b1697dd7) Thanks [@karstens](https://github.com/karstens)! - Making adjustments to bring the schema more in line with what was on the spectrum website. - -## 0.0.0-schema-20240620220450 - -### Minor Changes - -- [#353](https://github.com/adobe/spectrum-tokens/pull/353) [`64379eb`](https://github.com/adobe/spectrum-tokens/commit/64379ebeaf9402fe77ca1adfd020f42df60c60d9) Thanks [@karstens](https://github.com/karstens)! - Added schema for search-field and fixed some path bugs in testing - -## 0.0.0-schema-20240618053842 - -### Minor Changes - -- [#353](https://github.com/adobe/spectrum-tokens/pull/353) [`b5c1579`](https://github.com/adobe/spectrum-tokens/commit/b5c15792ec5f5e5c269bfa7bf58af3df42e648c1) Thanks [@karstens](https://github.com/karstens)! - Initial release - -## 0.0.0-schema-20240614194147 - -### Patch Changes - -- [#353](https://github.com/adobe/spectrum-tokens/pull/353) [`9805167`](https://github.com/adobe/spectrum-tokens/commit/980516791c0bef9e2f0bbeffe6515f103f3ad7a2) Thanks [@karstens](https://github.com/karstens)! - fixed some bugs - -## 0.0.0-schema-20240613154750 - -### Patch Changes - -- [#353](https://github.com/adobe/spectrum-tokens/pull/353) [`6ff5ad7`](https://github.com/adobe/spectrum-tokens/commit/6ff5ad7a75356f4b93d07a2818b357da19ce5b4b) Thanks [@karstens](https://github.com/karstens)! - Initial release diff --git a/packages/component-schemas/schemas/components/step-list.json b/packages/component-schemas/schemas/components/steplist.json similarity index 80% rename from packages/component-schemas/schemas/components/step-list.json rename to packages/component-schemas/schemas/components/steplist.json index 246ae7a7..5f69c471 100644 --- a/packages/component-schemas/schemas/components/step-list.json +++ b/packages/component-schemas/schemas/components/steplist.json @@ -1,11 +1,11 @@ { "$schema": "https://opensource.adobe.com/spectrum-tokens/schemas/component.json", - "$id": "https://opensource.adobe.com/spectrum-tokens/schemas/components/step-list.json", - "title": "Step list", - "description": "Step lists display progress through a sequence of steps, with each step marked as completed, current, or incomplete.", + "$id": "https://opensource.adobe.com/spectrum-tokens/schemas/components/steplist.json", + "title": "Steplist", + "description": "Steplists display progress through a sequence of steps, with each step marked as completed, current, or incomplete.", "meta": { "category": "navigation", - "documentationUrl": "https://spectrum.adobe.com/page/step-list/" + "documentationUrl": "https://spectrum.adobe.com/page/steplist/" }, "type": "object", "properties": { @@ -16,7 +16,7 @@ }, "items": { "type": "array", - "description": "An array of step items in the step list.", + "description": "An array of step items in the steplist.", "items": { "type": "object", "properties": { From c66854e00608f6ec73347b2a2fbd40d68ec9c43d Mon Sep 17 00:00:00 2001 From: Garth Braithwaite Date: Mon, 29 Sep 2025 15:20:08 -0600 Subject: [PATCH 6/6] feat(component-schemas): add $ref resolution in allOf properties - Add resolveRefs function to resolve $ref references in allOf arrays - Update getSchemaBySlug and getAllSchemas to return resolved schemas - Merge baseCard properties directly into card variant properties - Add comprehensive test suite for ref resolution functionality - Update existing cards inheritance tests to work with resolved structure - All 42 tests passing including 6 new ref resolution tests This makes schemas more developer-friendly by showing inherited properties directly instead of requiring $ref traversal. --- packages/component-schemas/index.js | 89 +++++++- .../test/cards-inheritance.test.js | 69 +++--- .../test/ref-resolution.test.js | 202 ++++++++++++++++++ 3 files changed, 326 insertions(+), 34 deletions(-) create mode 100644 packages/component-schemas/test/ref-resolution.test.js diff --git a/packages/component-schemas/index.js b/packages/component-schemas/index.js index 72f580db..7ddce052 100644 --- a/packages/component-schemas/index.js +++ b/packages/component-schemas/index.js @@ -25,6 +25,89 @@ export const readJson = async (fileName) => export const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); +/** + * Resolves $ref references in allOf properties by merging referenced schemas + * @param {Object} schema - The schema object to resolve references in + * @return {Object} - Schema with resolved references + */ +export const resolveRefs = (schema) => { + if (!schema || typeof schema !== "object") { + return schema; + } + + // Create a deep copy to avoid mutating the original + const resolved = JSON.parse(JSON.stringify(schema)); + + // Helper function to resolve refs in allOf arrays + const resolveAllOfRefs = (obj) => { + if (!obj || typeof obj !== "object") { + return obj; + } + + // Handle allOf arrays specifically + if (Array.isArray(obj.allOf)) { + const resolvedAllOf = obj.allOf.map((item) => { + if (item.$ref && item.$ref.startsWith("#/definitions/")) { + const refPath = item.$ref.replace("#/definitions/", ""); + const referencedSchema = resolved.definitions?.[refPath]; + if (referencedSchema) { + // Return the referenced schema properties merged with any additional properties + const { $ref, ...additionalProps } = item; + return { + ...JSON.parse(JSON.stringify(referencedSchema)), + ...additionalProps, + }; + } + } + return resolveAllOfRefs(item); + }); + + // Merge all resolved schemas in allOf + const mergedSchema = {}; + resolvedAllOf.forEach((resolvedItem) => { + if (resolvedItem && typeof resolvedItem === "object") { + // Deep merge properties + if (resolvedItem.properties) { + mergedSchema.properties = { + ...mergedSchema.properties, + ...resolvedItem.properties, + }; + } + if (resolvedItem.required) { + mergedSchema.required = [ + ...(mergedSchema.required || []), + ...(Array.isArray(resolvedItem.required) + ? resolvedItem.required + : [resolvedItem.required]), + ]; + } + // Merge other properties + Object.keys(resolvedItem).forEach((key) => { + if (key !== "properties" && key !== "required") { + mergedSchema[key] = resolvedItem[key]; + } + }); + } + }); + + // Replace allOf with the merged properties + const { allOf, ...otherProps } = obj; + return { ...mergedSchema, ...otherProps }; + } + + // Recursively process nested objects + for (const [key, value] of Object.entries(obj)) { + if (typeof value === "object" && value !== null) { + obj[key] = resolveAllOfRefs(value); + } + } + + return obj; + }; + + return resolveAllOfRefs(resolved); +}; + export const schemaFileNames = await glob( `${resolve(__dirname, "./schemas")}/**/*.json`, ); @@ -73,10 +156,10 @@ export const getAllSchemas = async () => { Object.hasOwn(data.meta, "documentationUrl") ) { return { - ...data, + ...resolveRefs(data), ...{ slug: getSlugFromDocumentationUrl(data.meta.documentationUrl) }, }; - } else return data; + } else return resolveRefs(data); }), ); }; @@ -91,5 +174,5 @@ export const getSchemaBySlug = async (slug) => { throw new Error(`Schema not found for slug: ${slug}`); } delete schema.slug; - return schema; + return resolveRefs(schema); }; diff --git a/packages/component-schemas/test/cards-inheritance.test.js b/packages/component-schemas/test/cards-inheritance.test.js index 0ded3103..45ba2120 100644 --- a/packages/component-schemas/test/cards-inheritance.test.js +++ b/packages/component-schemas/test/cards-inheritance.test.js @@ -59,52 +59,59 @@ test("all card variants should inherit baseCard properties", async (t) => { ); }); -test("gallery variant should include baseCard reference", async (t) => { +test("gallery variant should include baseCard properties", async (t) => { const cardsSchema = await getSchemaBySlug("cards"); const galleryVariant = cardsSchema.oneOf.find( (variant) => - variant.allOf && - variant.allOf.some( - (item) => - item.properties && - item.properties.variant && - item.properties.variant.const === "gallery", - ), + variant.properties && + variant.properties.variant && + variant.properties.variant.const === "gallery", ); t.truthy(galleryVariant, "Gallery variant should exist"); - t.true( - Array.isArray(galleryVariant.allOf), - "Gallery variant should use allOf structure", - ); + t.truthy(galleryVariant.properties, "Gallery variant should have properties"); - const hasBaseCardRef = galleryVariant.allOf.some( - (item) => item["$ref"] === "#/definitions/baseCard", - ); + // Check that baseCard properties are resolved directly + const hasBaseCardProperties = + galleryVariant.properties.size !== undefined && + galleryVariant.properties.state !== undefined && + galleryVariant.properties.isSelected !== undefined && + galleryVariant.properties.isQuiet !== undefined && + galleryVariant.properties.isDisabled !== undefined; - t.true(hasBaseCardRef, "Gallery variant should reference baseCard"); + t.true( + hasBaseCardProperties, + "Gallery variant should have baseCard properties resolved", + ); }); test("all card variants should have consistent structure", async (t) => { const cardsSchema = await getSchemaBySlug("cards"); const variants = cardsSchema.oneOf; - // All variants should either: - // 1. Use allOf with baseCard reference, OR - // 2. Be the gallery variant (which now uses allOf) + // All variants should have resolved baseCard properties directly for (const variant of variants) { - if (variant.allOf) { - const hasBaseCardRef = variant.allOf.some( - (item) => item["$ref"] === "#/definitions/baseCard", - ); - t.true( - hasBaseCardRef, - `Variant should reference baseCard: ${JSON.stringify(variant)}`, - ); - } else { - // This should only be the old gallery variant structure, which we've fixed - t.fail(`Variant should use allOf structure: ${JSON.stringify(variant)}`); - } + t.truthy( + variant.properties, + `Variant should have properties: ${JSON.stringify(variant)}`, + ); + + // Check that baseCard properties are resolved + const hasBaseCardProperties = + variant.properties.size !== undefined && + variant.properties.state !== undefined && + variant.properties.isSelected !== undefined; + + t.true( + hasBaseCardProperties, + `Variant should have baseCard properties resolved: ${JSON.stringify(variant)}`, + ); + + // Should not have allOf anymore (refs should be resolved) + t.falsy( + variant.allOf, + `Variant should not have allOf after ref resolution: ${JSON.stringify(variant)}`, + ); } }); diff --git a/packages/component-schemas/test/ref-resolution.test.js b/packages/component-schemas/test/ref-resolution.test.js new file mode 100644 index 00000000..ee4b3422 --- /dev/null +++ b/packages/component-schemas/test/ref-resolution.test.js @@ -0,0 +1,202 @@ +/* +Copyright 2024 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 test from "ava"; +import { getSchemaBySlug, resolveRefs } from "../index.js"; + +test("resolveRefs should resolve $ref in allOf arrays", (t) => { + const schema = { + definitions: { + baseCard: { + type: "object", + properties: { + size: { type: "string", enum: ["s", "m", "l"] }, + state: { type: "string", enum: ["default", "hover"] }, + }, + }, + }, + oneOf: [ + { + allOf: [ + { $ref: "#/definitions/baseCard" }, + { + properties: { + variant: { const: "test" }, + title: { type: "string" }, + }, + }, + ], + }, + ], + }; + + const resolved = resolveRefs(schema); + + t.true(Array.isArray(resolved.oneOf)); + t.is(resolved.oneOf.length, 1); + + const variant = resolved.oneOf[0]; + t.truthy(variant.properties); + t.truthy(variant.properties.size); + t.truthy(variant.properties.state); + t.truthy(variant.properties.variant); + t.truthy(variant.properties.title); + + // Should not have allOf anymore + t.falsy(variant.allOf); +}); + +test("resolveRefs should preserve oneOf structure", (t) => { + const schema = { + definitions: { + baseCard: { + type: "object", + properties: { + size: { type: "string" }, + }, + }, + }, + oneOf: [ + { + allOf: [ + { $ref: "#/definitions/baseCard" }, + { properties: { variant: { const: "test1" } } }, + ], + }, + { + allOf: [ + { $ref: "#/definitions/baseCard" }, + { properties: { variant: { const: "test2" } } }, + ], + }, + ], + }; + + const resolved = resolveRefs(schema); + + t.true(Array.isArray(resolved.oneOf)); + t.is(resolved.oneOf.length, 2); + t.is(resolved.oneOf[0].properties.variant.const, "test1"); + t.is(resolved.oneOf[1].properties.variant.const, "test2"); +}); + +test("getSchemaBySlug should return resolved refs for cards", async (t) => { + const cardsSchema = await getSchemaBySlug("cards"); + + t.truthy(cardsSchema); + t.true(Array.isArray(cardsSchema.oneOf)); + + // Find horizontal variant + const horizontalVariant = cardsSchema.oneOf.find( + (variant) => variant.properties?.variant?.const === "horizontal", + ); + + t.truthy(horizontalVariant); + t.truthy(horizontalVariant.properties); + + // Should have baseCard properties directly + t.truthy(horizontalVariant.properties.size); + t.truthy(horizontalVariant.properties.state); + t.truthy(horizontalVariant.properties.isSelected); + t.truthy(horizontalVariant.properties.isQuiet); + t.truthy(horizontalVariant.properties.isDisabled); + t.truthy(horizontalVariant.properties.hideCheckbox); + t.truthy(horizontalVariant.properties.actionLabel); + t.truthy(horizontalVariant.properties.metadata); + + // Should have variant-specific properties + t.truthy(horizontalVariant.properties.title); + t.truthy(horizontalVariant.properties.thumbnail); + t.truthy(horizontalVariant.properties.details); + + // Should not have allOf anymore + t.falsy(horizontalVariant.allOf); +}); + +test("getSchemaBySlug should return resolved refs for gallery cards", async (t) => { + const cardsSchema = await getSchemaBySlug("cards"); + + // Find gallery variant + const galleryVariant = cardsSchema.oneOf.find( + (variant) => variant.properties?.variant?.const === "gallery", + ); + + t.truthy(galleryVariant); + t.truthy(galleryVariant.properties); + + // Should have baseCard properties directly + t.truthy(galleryVariant.properties.size); + t.truthy(galleryVariant.properties.state); + t.truthy(galleryVariant.properties.isSelected); + + // Should have variant-specific properties + t.truthy(galleryVariant.properties.images); + t.truthy(Array.isArray(galleryVariant.required)); + t.true(galleryVariant.required.includes("images")); + + // Should not have allOf anymore + t.falsy(galleryVariant.allOf); +}); + +test("resolveRefs should handle nested allOf structures", (t) => { + const schema = { + definitions: { + base: { + type: "object", + properties: { + id: { type: "string" }, + }, + }, + }, + allOf: [ + { $ref: "#/definitions/base" }, + { + properties: { + name: { type: "string" }, + }, + }, + ], + }; + + const resolved = resolveRefs(schema); + + t.truthy(resolved.properties); + t.truthy(resolved.properties.id); + t.truthy(resolved.properties.name); + t.falsy(resolved.allOf); +}); + +test("resolveRefs should not mutate original schema", (t) => { + const originalSchema = { + definitions: { + base: { + type: "object", + properties: { + id: { type: "string" }, + }, + }, + }, + allOf: [ + { $ref: "#/definitions/base" }, + { properties: { name: { type: "string" } } }, + ], + }; + + const resolved = resolveRefs(originalSchema); + + // Original should still have allOf + t.truthy(originalSchema.allOf); + t.is(originalSchema.allOf.length, 2); + + // Resolved should not have allOf + t.falsy(resolved.allOf); +});