@astryxdesign/core 0.6.2 → 0.6.3-canary.039a67e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +81 -0
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +33 -12
- package/dist/BottomSheet/BottomSheet.d.ts +1 -1
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +3 -1
- package/dist/BottomSheet/BottomSheetPanel.d.ts +7 -5
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +56 -19
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +13 -28
- package/dist/Chat/ChatComposer.d.ts.map +1 -1
- package/dist/Chat/ChatComposer.js +10 -5
- package/dist/Chat/ChatComposerDrawer.d.ts +19 -5
- package/dist/Chat/ChatComposerDrawer.d.ts.map +1 -1
- package/dist/Chat/ChatComposerDrawer.js +48 -23
- package/dist/Chat/ChatComposerInput.d.ts +11 -3
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +63 -16
- package/dist/Chat/ChatDictationButton.d.ts +3 -2
- package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
- package/dist/Chat/ChatDictationButton.js +6 -4
- package/dist/Chat/ChatLayout.d.ts +19 -0
- package/dist/Chat/ChatLayout.d.ts.map +1 -1
- package/dist/Chat/ChatLayout.js +19 -0
- package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
- package/dist/Chat/ChatLayoutScrollButton.js +44 -34
- package/dist/Chat/ChatMessageBubble.d.ts +2 -0
- package/dist/Chat/ChatMessageBubble.d.ts.map +1 -1
- package/dist/Chat/ChatMessageBubble.js +5 -3
- package/dist/Chat/ChatMessageList.d.ts +2 -1
- package/dist/Chat/ChatMessageList.d.ts.map +1 -1
- package/dist/Chat/ChatMessageList.js +4 -3
- package/dist/Chat/ChatMessageMetadata.d.ts +5 -4
- package/dist/Chat/ChatMessageMetadata.d.ts.map +1 -1
- package/dist/Chat/ChatMessageMetadata.js +12 -9
- package/dist/Chat/ChatSendButton.d.ts +2 -1
- package/dist/Chat/ChatSendButton.d.ts.map +1 -1
- package/dist/Chat/ChatSendButton.js +6 -4
- package/dist/Chat/ChatSystemMessage.d.ts +3 -3
- package/dist/Chat/ChatSystemMessage.d.ts.map +1 -1
- package/dist/Chat/ChatSystemMessage.js +2 -2
- package/dist/Chat/ChatTokenizedText.d.ts +6 -6
- package/dist/Chat/ChatTokenizedText.js +10 -6
- package/dist/Chat/ChatToolCalls.d.ts +7 -9
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +43 -26
- package/dist/Chat/index.d.ts +1 -1
- package/dist/Chat/index.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +12 -2
- package/dist/Citation/Citation.d.ts +1 -1
- package/dist/Citation/Citation.d.ts.map +1 -1
- package/dist/Citation/Citation.js +3 -2
- package/dist/CodeBlock/highlightRanges.d.ts.map +1 -1
- package/dist/CodeBlock/highlightRanges.js +14 -3
- package/dist/Collapsible/Collapsible.d.ts.map +1 -1
- package/dist/Collapsible/Collapsible.js +6 -1
- package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +1 -1
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +1 -1
- package/dist/DateInput/DateInput.d.ts +25 -5
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +29 -17
- package/dist/DateInput/NativeDateField.d.ts.map +1 -1
- package/dist/DateInput/NativeDateField.js +16 -16
- package/dist/DateInput/TouchDateField.d.ts.map +1 -1
- package/dist/DateInput/TouchDateField.js +2 -2
- package/dist/DateInput/index.d.ts +1 -1
- package/dist/DateInput/index.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +4 -4
- package/dist/DateTimeInput/DateTimeInput.d.ts +32 -15
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +55 -30
- package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
- package/dist/DateTimeInput/TouchDateTimeField.js +4 -4
- package/dist/DateTimeInput/index.d.ts +1 -1
- package/dist/DateTimeInput/index.d.ts.map +1 -1
- package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts +9 -3
- package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts.map +1 -1
- package/dist/DateTimeInput/nativePickerSegmentStyles.js +3 -3
- package/dist/Dialog/DialogHeader.d.ts +8 -1
- package/dist/Dialog/DialogHeader.d.ts.map +1 -1
- package/dist/Dialog/DialogHeader.js +13 -4
- package/dist/Field/InputClearButton.d.ts +4 -2
- package/dist/Field/InputClearButton.d.ts.map +1 -1
- package/dist/Field/InputClearButton.js +4 -2
- package/dist/Field/PanelSearchInput.d.ts.map +1 -1
- package/dist/Field/PanelSearchInput.js +1 -1
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +7 -7
- package/dist/Icon/Icon.d.ts +6 -31
- package/dist/Icon/Icon.d.ts.map +1 -1
- package/dist/Icon/Icon.js +8 -71
- package/dist/Icon/IconDefaultSizeContext.d.ts +4 -0
- package/dist/Icon/IconDefaultSizeContext.d.ts.map +1 -0
- package/dist/Icon/IconDefaultSizeContext.js +18 -0
- package/dist/Icon/IconSize.stylex.d.ts +65 -0
- package/dist/Icon/IconSize.stylex.d.ts.map +1 -0
- package/dist/Icon/IconSize.stylex.js +75 -0
- package/dist/Item/Item.d.ts +1 -1
- package/dist/Item/Item.d.ts.map +1 -1
- package/dist/Item/Item.js +4 -3
- package/dist/Kbd/Kbd.d.ts +5 -3
- package/dist/Kbd/Kbd.d.ts.map +1 -1
- package/dist/Kbd/Kbd.js +36 -42
- package/dist/Link/Link.d.ts.map +1 -1
- package/dist/Link/Link.js +6 -2
- package/dist/Link/useLinkComponent.d.ts +5 -15
- package/dist/Link/useLinkComponent.d.ts.map +1 -1
- package/dist/Link/useLinkComponent.js +34 -51
- package/dist/List/List.d.ts +19 -1
- package/dist/List/List.d.ts.map +1 -1
- package/dist/List/List.js +9 -2
- package/dist/List/ListContext.d.ts +1 -0
- package/dist/List/ListContext.d.ts.map +1 -1
- package/dist/List/ListContext.js +2 -1
- package/dist/List/ListItem.d.ts +1 -1
- package/dist/List/ListItem.d.ts.map +1 -1
- package/dist/List/ListItem.js +9 -2
- package/dist/Markdown/Markdown.d.ts +8 -2
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.fr23.bench.d.ts +2 -0
- package/dist/Markdown/Markdown.fr23.bench.d.ts.map +1 -0
- package/dist/Markdown/Markdown.fr23.bench.js +150 -0
- package/dist/Markdown/Markdown.fr23.sampling.d.ts +2 -0
- package/dist/Markdown/Markdown.fr23.sampling.d.ts.map +1 -0
- package/dist/Markdown/Markdown.fr23.sampling.js +43 -0
- package/dist/Markdown/Markdown.js +262 -109
- package/dist/Markdown/ast.d.ts +162 -0
- package/dist/Markdown/ast.d.ts.map +1 -0
- package/dist/Markdown/ast.js +74 -0
- package/dist/Markdown/index.d.ts +5 -3
- package/dist/Markdown/index.d.ts.map +1 -1
- package/dist/Markdown/index.js +3 -2
- package/dist/Markdown/parser/index.d.ts +10 -0
- package/dist/Markdown/parser/index.d.ts.map +1 -0
- package/dist/Markdown/parser/index.js +10 -0
- package/dist/Markdown/parser.d.ts +126 -58
- package/dist/Markdown/parser.d.ts.map +1 -1
- package/dist/Markdown/parser.js +726 -177
- package/dist/Markdown/plugins/frontmatter.d.ts +32 -0
- package/dist/Markdown/plugins/frontmatter.d.ts.map +1 -0
- package/dist/Markdown/plugins/frontmatter.js +132 -0
- package/dist/Markdown/plugins/index.d.ts +17 -0
- package/dist/Markdown/plugins/index.d.ts.map +1 -0
- package/dist/Markdown/plugins/index.js +14 -0
- package/dist/Markdown/plugins/protocol.d.ts +191 -0
- package/dist/Markdown/plugins/protocol.d.ts.map +1 -0
- package/dist/Markdown/plugins/protocol.js +957 -0
- package/dist/Markdown/plugins/semanticFence.d.ts +36 -0
- package/dist/Markdown/plugins/semanticFence.d.ts.map +1 -0
- package/dist/Markdown/plugins/semanticFence.js +213 -0
- package/dist/Markdown/plugins/softBreaks.d.ts +3 -0
- package/dist/Markdown/plugins/softBreaks.d.ts.map +1 -0
- package/dist/Markdown/plugins/softBreaks.js +172 -0
- package/dist/Markdown/plugins/sourceDecoration.d.ts +76 -0
- package/dist/Markdown/plugins/sourceDecoration.d.ts.map +1 -0
- package/dist/Markdown/plugins/sourceDecoration.js +393 -0
- package/dist/Markdown/plugins/textTransform.d.ts +30 -0
- package/dist/Markdown/plugins/textTransform.d.ts.map +1 -0
- package/dist/Markdown/plugins/textTransform.js +374 -0
- package/dist/Markdown/remark.d.ts +196 -0
- package/dist/Markdown/remark.d.ts.map +1 -0
- package/dist/Markdown/remark.js +1062 -0
- package/dist/Markdown/url.d.ts +6 -0
- package/dist/Markdown/url.d.ts.map +1 -0
- package/dist/Markdown/url.js +31 -0
- package/dist/Markdown/utils.d.ts +4 -2
- package/dist/Markdown/utils.d.ts.map +1 -1
- package/dist/Markdown/utils.js +2 -1
- package/dist/MetadataList/MetadataList.js +3 -3
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +4 -4
- package/dist/Outline/index.d.ts +1 -0
- package/dist/Outline/index.d.ts.map +1 -1
- package/dist/Outline/parseOutlineFromMarkdown.d.ts +7 -1
- package/dist/Outline/parseOutlineFromMarkdown.d.ts.map +1 -1
- package/dist/Outline/parseOutlineFromMarkdown.js +14 -8
- package/dist/Outline/useOutlineFromMarkdown.d.ts +3 -1
- package/dist/Outline/useOutlineFromMarkdown.d.ts.map +1 -1
- package/dist/Outline/useOutlineFromMarkdown.js +8 -3
- package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchEditPopover.js +46 -30
- package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchValueEditor.js +5 -0
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +36 -31
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +13 -1
- package/dist/ScrollableArea/ScrollableArea.d.ts +1 -0
- package/dist/ScrollableArea/ScrollableArea.d.ts.map +1 -1
- package/dist/ScrollableArea/ScrollableArea.js +1 -0
- package/dist/SegmentedControl/SegmentedControl.d.ts +1 -1
- package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControl.js +1 -0
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +5 -5
- package/dist/Selector/Selector.d.ts +9 -7
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +16 -7
- package/dist/SideNav/SideNav.d.ts +2 -1
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +9 -3
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +28 -12
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +23 -7
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +11 -0
- package/dist/TabList/Tab.d.ts +1 -1
- package/dist/TabList/Tab.d.ts.map +1 -1
- package/dist/TabList/Tab.js +20 -7
- package/dist/Table/Table.d.ts +1 -0
- package/dist/Table/Table.d.ts.map +1 -1
- package/dist/Table/Table.js +29 -13
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +1 -1
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +2 -2
- package/dist/TimeInput/TimeInput.d.ts +30 -14
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +61 -13
- package/dist/TimeInput/TouchTimeField.d.ts +11 -0
- package/dist/TimeInput/TouchTimeField.d.ts.map +1 -0
- package/dist/TimeInput/TouchTimeField.js +417 -0
- package/dist/TimeInput/index.d.ts +1 -1
- package/dist/TimeInput/index.d.ts.map +1 -1
- package/dist/Timer/Timer.d.ts +49 -0
- package/dist/Timer/Timer.d.ts.map +1 -0
- package/dist/Timer/Timer.js +158 -0
- package/dist/Timer/index.d.ts +9 -0
- package/dist/Timer/index.d.ts.map +1 -0
- package/dist/Timer/index.js +10 -0
- package/dist/ToggleButton/ToggleButton.d.ts +9 -6
- package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButton/ToggleButton.js +15 -27
- package/dist/TopNav/TopNavMenu.d.ts +1 -1
- package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMenu.js +3 -2
- package/dist/TreeList/TreeList.d.ts.map +1 -1
- package/dist/TreeList/TreeList.js +3 -0
- package/dist/TreeList/TreeListItem.d.ts +6 -1
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +12 -2
- package/dist/TreeList/TreeListTypes.d.ts +8 -1
- package/dist/TreeList/TreeListTypes.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +16 -7
- package/dist/astryx.css +27 -11
- package/dist/hooks/scrollKeyboardDelegation.d.ts +3 -0
- package/dist/hooks/scrollKeyboardDelegation.d.ts.map +1 -0
- package/dist/hooks/scrollKeyboardDelegation.js +146 -0
- package/dist/hooks/useClickableContainer.d.ts +10 -1
- package/dist/hooks/useClickableContainer.d.ts.map +1 -1
- package/dist/hooks/useClickableContainer.js +15 -4
- package/dist/hooks/useScrollableArea.d.ts +6 -2
- package/dist/hooks/useScrollableArea.d.ts.map +1 -1
- package/dist/hooks/useScrollableArea.js +17 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/theme/declarationBoundary.d.ts +17 -0
- package/dist/theme/declarationBoundary.d.ts.map +1 -0
- package/dist/theme/declarationBoundary.js +343 -0
- package/dist/theme/defineTheme.d.ts +3 -0
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/defineTheme.js +4 -1
- package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
- package/dist/theme/derivedVarRegistry.js +4 -0
- package/dist/theme/generateThemeRules.d.ts +15 -7
- package/dist/theme/generateThemeRules.d.ts.map +1 -1
- package/dist/theme/generateThemeRules.js +143 -35
- package/dist/theme/themeAdaptations.d.ts +5 -3
- package/dist/theme/themeAdaptations.d.ts.map +1 -1
- package/dist/theme/themeAdaptations.js +33 -5
- package/dist/utils/inputPresentation.d.ts +55 -0
- package/dist/utils/inputPresentation.d.ts.map +1 -0
- package/dist/utils/inputPresentation.js +108 -0
- package/dist/utils/interactionOverlay.stylex.d.ts +8 -0
- package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
- package/dist/utils/interactionOverlay.stylex.js +9 -0
- package/dist/utils/safeUrl.d.ts +11 -0
- package/dist/utils/safeUrl.d.ts.map +1 -0
- package/dist/utils/safeUrl.js +78 -0
- package/locales/af-ZA.json +530 -1
- package/locales/ar-SA.json +490 -1
- package/locales/ca-ES.json +514 -1
- package/locales/cs-CZ.json +498 -1
- package/locales/da-DK.json +522 -1
- package/locales/de-DE.json +522 -1
- package/locales/el-GR.json +498 -1
- package/locales/es-ES.json +510 -1
- package/locales/fi-FI.json +498 -1
- package/locales/fr-FR.json +534 -1
- package/locales/he-IL.json +498 -1
- package/locales/hu-HU.json +498 -1
- package/locales/it-IT.json +510 -1
- package/locales/ja-JP.json +490 -1
- package/locales/ko-KR.json +498 -1
- package/locales/nl-NL.json +534 -1
- package/locales/no-NO.json +510 -1
- package/locales/pl-PL.json +506 -1
- package/locales/pt-BR.json +506 -1
- package/locales/pt-PT.json +506 -1
- package/locales/ro-RO.json +506 -1
- package/locales/ru-RU.json +498 -1
- package/locales/sr-SP.json +498 -1
- package/locales/sv-SE.json +502 -1
- package/locales/tr-TR.json +498 -1
- package/locales/uk-UA.json +498 -1
- package/locales/vi-VN.json +502 -1
- package/locales/zh-CN.json +490 -1
- package/locales/zh-TW.json +490 -1
- package/package.json +24 -4
- package/src/Avatar/Avatar.doc.mjs +1 -1
- package/src/Avatar/Avatar.test.tsx +30 -0
- package/src/Avatar/Avatar.tsx +38 -14
- package/src/BottomSheet/BottomSheet.doc.mjs +8 -1
- package/src/BottomSheet/BottomSheet.spec.md +46 -20
- package/src/BottomSheet/BottomSheet.test.tsx +6 -3
- package/src/BottomSheet/BottomSheet.tsx +3 -1
- package/src/BottomSheet/BottomSheetKeyboard.test.tsx +195 -0
- package/src/BottomSheet/BottomSheetPanel.test.tsx +11 -1
- package/src/BottomSheet/BottomSheetPanel.tsx +49 -16
- package/src/BottomSheet/__tests__/BottomSheetKeyboard.a11y.browser.spec.ts +344 -0
- package/src/Button/Button.doc.mjs +5 -5
- package/src/Button/Button.tsx +15 -13
- package/src/Button/__tests__/Button.a11y.chromium.spec.ts +17 -1
- package/src/Button/__tests__/Button.a11y.known-failures.ts +0 -29
- package/src/Button/__tests__/Button.a11y.renders.tsx +9 -2
- package/src/Button/__tests__/Button.a11y.states.ts +9 -0
- package/src/Chat/ChatComposer.doc.mjs +14 -0
- package/src/Chat/ChatComposer.spec.md +230 -0
- package/src/Chat/ChatComposer.test.tsx +57 -0
- package/src/Chat/ChatComposer.tsx +21 -9
- package/src/Chat/ChatComposerDrawer.doc.mjs +63 -5
- package/src/Chat/ChatComposerDrawer.spec.md +267 -0
- package/src/Chat/ChatComposerDrawer.test.tsx +81 -12
- package/src/Chat/ChatComposerDrawer.tsx +38 -9
- package/src/Chat/ChatComposerInput.doc.mjs +114 -26
- package/src/Chat/ChatComposerInput.spec.md +185 -0
- package/src/Chat/ChatComposerInput.test.tsx +113 -3
- package/src/Chat/ChatComposerInput.tsx +78 -13
- package/src/Chat/ChatComposerTokenElement.spec.md +152 -0
- package/src/Chat/ChatComposerTokenElement.test.tsx +85 -0
- package/src/Chat/ChatDictationButton.doc.mjs +3 -3
- package/src/Chat/ChatDictationButton.spec.md +160 -0
- package/src/Chat/ChatDictationButton.test.tsx +92 -27
- package/src/Chat/ChatDictationButton.tsx +8 -6
- package/src/Chat/ChatLayout.doc.mjs +12 -4
- package/src/Chat/ChatLayout.spec.md +271 -0
- package/src/Chat/ChatLayout.test.tsx +28 -0
- package/src/Chat/ChatLayout.tsx +19 -0
- package/src/Chat/ChatLayoutScrollButton.spec.md +338 -0
- package/src/Chat/ChatLayoutScrollButton.test.tsx +31 -0
- package/src/Chat/ChatLayoutScrollButton.tsx +31 -14
- package/src/Chat/ChatMessageBubble.doc.mjs +28 -15
- package/src/Chat/ChatMessageBubble.spec.md +263 -0
- package/src/Chat/ChatMessageBubble.test.tsx +93 -0
- package/src/Chat/ChatMessageBubble.tsx +8 -3
- package/src/Chat/ChatMessageList.spec.md +222 -0
- package/src/Chat/ChatMessageList.test.tsx +64 -2
- package/src/Chat/ChatMessageList.tsx +4 -3
- package/src/Chat/ChatMessageMetadata.doc.mjs +57 -3
- package/src/Chat/ChatMessageMetadata.spec.md +220 -0
- package/src/Chat/ChatMessageMetadata.test.tsx +34 -0
- package/src/Chat/ChatMessageMetadata.tsx +13 -13
- package/src/Chat/ChatSendButton.doc.mjs +32 -0
- package/src/Chat/ChatSendButton.spec.md +227 -0
- package/src/Chat/ChatSendButton.test.tsx +128 -8
- package/src/Chat/ChatSendButton.tsx +6 -4
- package/src/Chat/ChatSystemMessage.doc.mjs +115 -12
- package/src/Chat/ChatSystemMessage.spec.md +225 -0
- package/src/Chat/ChatSystemMessage.test.tsx +112 -20
- package/src/Chat/ChatSystemMessage.tsx +4 -6
- package/src/Chat/ChatTokenizedText.doc.mjs +96 -9
- package/src/Chat/ChatTokenizedText.spec.md +190 -0
- package/src/Chat/ChatTokenizedText.test.tsx +60 -1
- package/src/Chat/ChatTokenizedText.tsx +14 -8
- package/src/Chat/ChatToolCalls.doc.mjs +4 -1
- package/src/Chat/ChatToolCalls.spec.md +194 -0
- package/src/Chat/ChatToolCalls.test.tsx +83 -0
- package/src/Chat/ChatToolCalls.tsx +46 -21
- package/src/Chat/__tests__/ChatLayoutScrollButton.a11y.chromium.spec.ts +514 -0
- package/src/Chat/__tests__/ChatLayoutScrollButtonSurface.a11y.chromium.spec.ts +740 -0
- package/src/Chat/__tests__/ChatMessageBubble.a11y.chromium.spec.ts +457 -0
- package/src/Chat/__tests__/ChatMessageBubble.auditProvenance.test.ts +91 -0
- package/src/Chat/__tests__/ChatMessageBubble.auditProvenance.ts +91 -0
- package/src/Chat/__tests__/ChatMessageList.a11y.chromium.spec.ts +456 -0
- package/src/Chat/__tests__/ChatMessageMetadata.a11y.chromium.spec.ts +413 -0
- package/src/Chat/__tests__/ChatSendButton.a11y.chromium.spec.ts +1522 -0
- package/src/Chat/__tests__/ChatSystemMessage.a11y.chromium.spec.ts +1004 -0
- package/src/Chat/__tests__/ChatTokenizedText.a11y.chromium.spec.ts +573 -0
- package/src/Chat/index.ts +1 -0
- package/src/CheckboxInput/CheckboxInput.doc.mjs +11 -0
- package/src/CheckboxInput/CheckboxInput.test.tsx +34 -0
- package/src/CheckboxInput/CheckboxInput.tsx +21 -1
- package/src/Citation/Citation.doc.mjs +2 -2
- package/src/Citation/Citation.test.tsx +17 -0
- package/src/Citation/Citation.tsx +4 -2
- package/src/ClickableCard/ClickableCard.doc.mjs +2 -2
- package/src/ClickableCard/ClickableCard.test.tsx +102 -5
- package/src/CodeBlock/highlightRanges.test.ts +143 -1
- package/src/CodeBlock/highlightRanges.ts +15 -5
- package/src/Collapsible/Collapsible.doc.mjs +11 -0
- package/src/Collapsible/Collapsible.test.tsx +21 -0
- package/src/Collapsible/Collapsible.tsx +5 -0
- package/src/CommandPalette/CommandPaletteInput.tsx +6 -1
- package/src/ComplexSelector/ComplexSelector.tsx +6 -1
- package/src/DateInput/DateInput.doc.mjs +38 -9
- package/src/DateInput/DateInput.tsx +59 -17
- package/src/DateInput/NativeDateField.tsx +24 -8
- package/src/DateInput/Presentation.test.tsx +121 -0
- package/src/DateInput/TouchDateField.tsx +6 -2
- package/src/DateInput/index.ts +1 -0
- package/src/DateRangeInput/DateRangeInput.tsx +6 -1
- package/src/DateTimeInput/DateTimeInput.doc.mjs +28 -12
- package/src/DateTimeInput/DateTimeInput.tsx +93 -37
- package/src/DateTimeInput/Presentation.test.tsx +89 -0
- package/src/DateTimeInput/TouchDateTimeField.tsx +6 -1
- package/src/DateTimeInput/index.ts +1 -0
- package/src/DateTimeInput/nativePickerSegmentStyles.ts +24 -8
- package/src/Dialog/Dialog.doc.mjs +2 -0
- package/src/Dialog/DialogHeader.doc.mjs +22 -0
- package/src/Dialog/DialogHeader.test.tsx +167 -3
- package/src/Dialog/DialogHeader.tsx +37 -6
- package/src/Dialog/modules/DialogHeader.spec.md +101 -42
- package/src/DropdownMenu/DropdownMenu.test.tsx +5 -2
- package/src/Field/InputClearButton.test.tsx +17 -3
- package/src/Field/InputClearButton.tsx +5 -2
- package/src/Field/PanelSearchInput.tsx +5 -2
- package/src/FieldStatus/__tests__/StatusMessage.a11y.states.ts +1 -1
- package/src/FileInput/FileInput.tsx +11 -2
- package/src/Icon/Icon.doc.mjs +7 -5
- package/src/Icon/Icon.spec.md +81 -40
- package/src/Icon/Icon.test.tsx +6 -3
- package/src/Icon/Icon.tsx +20 -68
- package/src/Icon/IconDefaultSizeContext.ts +23 -0
- package/src/Icon/IconSize.stylex.ts +70 -0
- package/src/IconButton/IconButton.doc.mjs +3 -3
- package/src/IconButton/IconButton.test.tsx +40 -0
- package/src/Item/Item.doc.mjs +6 -2
- package/src/Item/Item.tsx +9 -3
- package/src/Kbd/Kbd.doc.mjs +3 -3
- package/src/Kbd/Kbd.test.tsx +57 -1
- package/src/Kbd/Kbd.tsx +57 -37
- package/src/Layout/LayoutFooter.doc.mjs +17 -0
- package/src/Link/Link.doc.mjs +17 -4
- package/src/Link/Link.test.tsx +24 -0
- package/src/Link/Link.tsx +5 -0
- package/src/Link/LinkProvider.doc.mjs +2 -2
- package/src/Link/__tests__/Link.navigation.a11y.chromium.spec.ts +261 -0
- package/src/Link/useLinkComponent.test.tsx +333 -0
- package/src/Link/useLinkComponent.ts +29 -57
- package/src/List/List.doc.mjs +6 -0
- package/src/List/List.spec.md +200 -0
- package/src/List/List.test.tsx +117 -1
- package/src/List/List.tsx +22 -2
- package/src/List/ListContext.tsx +3 -1
- package/src/List/ListItem.doc.mjs +2 -2
- package/src/List/ListItem.source-build.test.mjs +41 -0
- package/src/List/ListItem.tsx +26 -2
- package/src/Markdown/Markdown.doc.mjs +300 -2
- package/src/Markdown/Markdown.fr23.bench.ts +174 -0
- package/src/Markdown/Markdown.fr23.perf.test.ts +249 -0
- package/src/Markdown/Markdown.fr23.sampling.ts +49 -0
- package/src/Markdown/Markdown.helpers.perf.test.ts +489 -0
- package/src/Markdown/Markdown.public.test.ts +130 -3
- package/src/Markdown/Markdown.renderBoundary.test.tsx +72 -0
- package/src/Markdown/Markdown.spec.md +210 -45
- package/src/Markdown/Markdown.test.tsx +87 -3
- package/src/Markdown/Markdown.tsx +414 -135
- package/src/Markdown/ast.test.ts +307 -0
- package/src/Markdown/ast.ts +337 -0
- package/src/Markdown/incremental.test.ts +43 -0
- package/src/Markdown/index.ts +37 -1
- package/src/Markdown/modules/remark.spec.md +269 -0
- package/src/Markdown/modules/softBreaks.spec.md +137 -0
- package/src/Markdown/parser/index.ts +64 -0
- package/src/Markdown/parser.public.test.ts +103 -0
- package/src/Markdown/parser.test.ts +38 -6
- package/src/Markdown/parser.ts +1221 -242
- package/src/Markdown/plugins/frontmatter.test.tsx +125 -0
- package/src/Markdown/plugins/frontmatter.ts +198 -0
- package/src/Markdown/plugins/index.ts +54 -0
- package/src/Markdown/plugins/protocol.test.tsx +1022 -0
- package/src/Markdown/plugins/protocol.ts +1618 -0
- package/src/Markdown/plugins/semanticFence.test.tsx +546 -0
- package/src/Markdown/plugins/semanticFence.ts +424 -0
- package/src/Markdown/plugins/softBreaks.test.tsx +172 -0
- package/src/Markdown/plugins/softBreaks.ts +169 -0
- package/src/Markdown/plugins/sourceDecoration.test.tsx +799 -0
- package/src/Markdown/plugins/sourceDecoration.ts +539 -0
- package/src/Markdown/plugins/textTransform.test.ts +467 -0
- package/src/Markdown/plugins/textTransform.ts +554 -0
- package/src/Markdown/remark.test.tsx +1877 -0
- package/src/Markdown/remark.ts +1574 -0
- package/src/Markdown/url.ts +36 -0
- package/src/Markdown/utils.ts +13 -1
- package/src/MetadataList/MetadataList.test.tsx +4 -2
- package/src/MetadataList/MetadataList.tsx +3 -3
- package/src/MultiSelector/MultiSelector.test.tsx +1 -35
- package/src/MultiSelector/__tests__/MultiSelector.listbox.a11y.test.tsx +128 -0
- package/src/NumberInput/NumberInput.tsx +6 -1
- package/src/Outline/Outline.doc.mjs +7 -5
- package/src/Outline/Outline.spec.md +1 -1
- package/src/Outline/index.ts +1 -0
- package/src/Outline/modules/parseOutlineFromMarkdown.spec.md +142 -0
- package/src/Outline/parseOutlineFromMarkdown.ts +40 -10
- package/src/Outline/useOutlineFromMarkdown.ts +15 -3
- package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
- package/src/PowerSearch/PowerSearchEditPopover.test.tsx +150 -1
- package/src/PowerSearch/PowerSearchEditPopover.tsx +51 -28
- package/src/PowerSearch/PowerSearchValueEditor.test.tsx +51 -0
- package/src/PowerSearch/PowerSearchValueEditor.tsx +4 -0
- package/src/ProgressBar/ProgressBar.test.tsx +29 -11
- package/src/ProgressBar/ProgressBar.tsx +59 -72
- package/src/RadioList/RadioList.doc.mjs +11 -0
- package/src/RadioList/RadioList.test.tsx +32 -0
- package/src/RadioList/RadioListItem.tsx +25 -1
- package/src/ScrollableArea/ScrollableArea.tsx +1 -0
- package/src/ScrollableArea/modules/useScrollableArea.spec.md +50 -22
- package/src/SegmentedControl/SegmentedControl.doc.mjs +2 -2
- package/src/SegmentedControl/SegmentedControl.test.tsx +61 -0
- package/src/SegmentedControl/SegmentedControl.tsx +2 -1
- package/src/SegmentedControl/SegmentedControlItem.tsx +6 -9
- package/src/Selector/Selector.doc.mjs +3 -3
- package/src/Selector/Selector.spec.md +51 -49
- package/src/Selector/Selector.test.tsx +61 -55
- package/src/Selector/Selector.tsx +34 -15
- package/src/Selector/__tests__/Listbox.a11y.chromium.spec.ts +130 -0
- package/src/Selector/__tests__/Listbox.a11y.dom.ts +38 -0
- package/src/Selector/__tests__/Listbox.a11y.inventory.test.ts +63 -0
- package/src/Selector/__tests__/Listbox.a11y.known-failures.ts +37 -0
- package/src/Selector/__tests__/Listbox.a11y.states.ts +232 -0
- package/src/Selector/__tests__/Selector.a11y.chromium.spec.ts +45 -0
- package/src/Selector/__tests__/Selector.listbox.a11y.test.tsx +121 -0
- package/src/SideNav/SideNav.doc.mjs +1 -1
- package/src/SideNav/SideNav.test.tsx +10 -0
- package/src/SideNav/SideNav.tsx +14 -2
- package/src/Slider/Slider.doc.mjs +17 -3
- package/src/Slider/Slider.test.tsx +221 -0
- package/src/Slider/Slider.tsx +40 -9
- package/src/Spinner/Spinner.test.tsx +4 -0
- package/src/Spinner/Spinner.tsx +31 -18
- package/src/Switch/Switch.doc.mjs +11 -0
- package/src/Switch/Switch.test.tsx +16 -0
- package/src/Switch/Switch.tsx +28 -0
- package/src/TabList/Tab.tsx +35 -7
- package/src/TabList/TabList.doc.mjs +11 -0
- package/src/TabList/TabList.spec.md +204 -0
- package/src/TabList/TabList.test.tsx +66 -0
- package/src/TabList/__tests__/Tabs.a11y.known-failures.ts +1 -34
- package/src/Table/Table.doc.mjs +1 -1
- package/src/Table/Table.spec.md +54 -40
- package/src/Table/Table.test.tsx +83 -10
- package/src/Table/Table.tsx +30 -22
- package/src/Table/__tests__/TableScroll.a11y.chromium.spec.ts +137 -0
- package/src/TextArea/TextArea.tsx +6 -1
- package/src/TextInput/TextInput.test.tsx +20 -1
- package/src/TextInput/TextInput.tsx +6 -1
- package/src/TimeInput/NativeTimeInput.test.tsx +36 -3
- package/src/TimeInput/Presentation.test.tsx +148 -0
- package/src/TimeInput/TimeInput.doc.mjs +21 -5
- package/src/TimeInput/TimeInput.tsx +102 -11
- package/src/TimeInput/TouchTimeField.tsx +572 -0
- package/src/TimeInput/index.ts +1 -0
- package/src/Timer/Timer.doc.mjs +183 -0
- package/src/Timer/Timer.spec.md +215 -0
- package/src/Timer/Timer.test.tsx +302 -0
- package/src/Timer/Timer.tsx +258 -0
- package/src/Timer/index.ts +11 -0
- package/src/ToggleButton/ToggleButton.doc.mjs +3 -3
- package/src/ToggleButton/ToggleButton.test.tsx +299 -4
- package/src/ToggleButton/ToggleButton.tsx +26 -33
- package/src/ToggleButton/ToggleButtonGroup.doc.mjs +3 -3
- package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +209 -0
- package/src/Token/Token.doc.mjs +2 -2
- package/src/Tokenizer/Tokenizer.spec.md +142 -75
- package/src/TopNav/TopNavMenu.doc.mjs +2 -2
- package/src/TopNav/TopNavMenu.test.tsx +30 -0
- package/src/TopNav/TopNavMenu.tsx +7 -2
- package/src/TreeList/TreeList.doc.mjs +3 -3
- package/src/TreeList/TreeList.spec.md +123 -75
- package/src/TreeList/TreeList.test.tsx +29 -0
- package/src/TreeList/TreeList.tsx +3 -0
- package/src/TreeList/TreeListItem.tsx +15 -1
- package/src/TreeList/TreeListTypes.ts +11 -1
- package/src/Typeahead/BaseTypeahead.spec.md +4 -3
- package/src/Typeahead/BaseTypeahead.tsx +21 -7
- package/src/Typeahead/Typeahead.test.tsx +53 -0
- package/src/__tests__/PressedState.a11y.chromium.spec.ts +813 -0
- package/src/__tests__/pressState.ts +93 -0
- package/src/hooks/scrollKeyboardDelegation.test.ts +155 -0
- package/src/hooks/scrollKeyboardDelegation.ts +233 -0
- package/src/hooks/useClickableContainer.test.tsx +236 -0
- package/src/hooks/useClickableContainer.ts +20 -4
- package/src/hooks/useScrollableArea.doc.mjs +15 -3
- package/src/hooks/useScrollableArea.test.tsx +59 -1
- package/src/hooks/useScrollableArea.ts +34 -10
- package/src/index.ts +3 -0
- package/src/inputFontFloor.test.ts +106 -0
- package/src/tailwind-theme.css +0 -6
- package/src/theme/Theme.test.tsx +32 -0
- package/src/theme/declarationBoundary.test.ts +126 -0
- package/src/theme/declarationBoundary.ts +347 -0
- package/src/theme/defineTheme.ts +4 -0
- package/src/theme/derivedVarRegistry.test.ts +7 -0
- package/src/theme/derivedVarRegistry.ts +1 -0
- package/src/theme/generateThemeRules.test.ts +526 -1
- package/src/theme/generateThemeRules.ts +231 -50
- package/src/theme/themeAdaptations.test.ts +211 -1
- package/src/theme/themeAdaptations.ts +50 -4
- package/src/theme/tokenValueCompat.test.ts +4 -1
- package/src/utils/inputPresentation.ts +143 -0
- package/src/utils/interactionOverlay.stylex.ts +20 -0
- package/src/utils/safeUrl.test.ts +88 -0
- package/src/utils/safeUrl.ts +85 -0
|
@@ -0,0 +1,1004 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file ChatSystemMessage.a11y.chromium.spec.ts
|
|
5
|
+
* @input Exact-head Storybook build plus checked-in ChatSystemMessage audit fixtures
|
|
6
|
+
* @output Chromium PNGs, canonical 10-sensor receipts, D7 pairs, and a fail-closed manifest
|
|
7
|
+
* @position Real-browser evidence for the ChatSystemMessage component audit
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {createHash} from 'node:crypto';
|
|
11
|
+
import {execFileSync} from 'node:child_process';
|
|
12
|
+
import * as fs from 'node:fs';
|
|
13
|
+
import * as path from 'node:path';
|
|
14
|
+
import {expect, test, type Browser, type Page} from '@playwright/test';
|
|
15
|
+
// @ts-expect-error -- pngjs ships no declarations; runtime support is pinned.
|
|
16
|
+
import {PNG} from 'pngjs';
|
|
17
|
+
import {holdMotionStill} from '@astryxdesign/a11y-spec/chromium';
|
|
18
|
+
import {
|
|
19
|
+
DEFAULT_STORYBOOK_DIR,
|
|
20
|
+
serveStorybook,
|
|
21
|
+
type StaticServer,
|
|
22
|
+
} from '@astryxdesign/a11y-spec/storybook';
|
|
23
|
+
|
|
24
|
+
const OUTPUT = path.resolve('test-results/chat-system-message-audit-evidence');
|
|
25
|
+
const STATES_STORY = 'a11y-chatsystemmessage-audit--states';
|
|
26
|
+
const NARROW_STORY = 'a11y-chatsystemmessage-audit--narrow';
|
|
27
|
+
|
|
28
|
+
type Mode = 'light' | 'dark';
|
|
29
|
+
type Direction = 'ltr';
|
|
30
|
+
type Rgba = {r: number; g: number; b: number; a: number};
|
|
31
|
+
|
|
32
|
+
interface AuditCase {
|
|
33
|
+
key: string;
|
|
34
|
+
storyId: string;
|
|
35
|
+
text: string;
|
|
36
|
+
variant: 'default' | 'divider';
|
|
37
|
+
expectedIconCount: number | null;
|
|
38
|
+
narrow: boolean;
|
|
39
|
+
expectWrap?: boolean;
|
|
40
|
+
nestedInLiveLog?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface ContrastPair {
|
|
44
|
+
part: 'text' | 'icon' | 'divider-line';
|
|
45
|
+
meaningful: boolean;
|
|
46
|
+
foreground: string;
|
|
47
|
+
backdrop: string;
|
|
48
|
+
ratio: number | null;
|
|
49
|
+
threshold: number | null;
|
|
50
|
+
exception: string | null;
|
|
51
|
+
passed: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface ImageReceipt {
|
|
55
|
+
file: string;
|
|
56
|
+
sha256: string;
|
|
57
|
+
width: number;
|
|
58
|
+
height: number;
|
|
59
|
+
distinctColors: number;
|
|
60
|
+
nonBlank: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const CASES: AuditCase[] = [
|
|
64
|
+
{
|
|
65
|
+
key: 'default',
|
|
66
|
+
storyId: STATES_STORY,
|
|
67
|
+
text: 'Conversation started',
|
|
68
|
+
variant: 'default',
|
|
69
|
+
expectedIconCount: 0,
|
|
70
|
+
narrow: false,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
key: 'default-icon',
|
|
74
|
+
storyId: STATES_STORY,
|
|
75
|
+
text: 'Messages are end-to-end encrypted',
|
|
76
|
+
variant: 'default',
|
|
77
|
+
expectedIconCount: 1,
|
|
78
|
+
narrow: false,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
key: 'divider',
|
|
82
|
+
storyId: STATES_STORY,
|
|
83
|
+
text: 'Today',
|
|
84
|
+
variant: 'divider',
|
|
85
|
+
expectedIconCount: 0,
|
|
86
|
+
narrow: false,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
key: 'divider-icon',
|
|
90
|
+
storyId: STATES_STORY,
|
|
91
|
+
text: 'March 15, 2026',
|
|
92
|
+
variant: 'divider',
|
|
93
|
+
// The public contract for this combination is unresolved. Record the
|
|
94
|
+
// observed count without freezing either candidate behavior in this test.
|
|
95
|
+
expectedIconCount: null,
|
|
96
|
+
narrow: false,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
key: 'nested-log',
|
|
100
|
+
storyId: STATES_STORY,
|
|
101
|
+
text: 'Conversation archived',
|
|
102
|
+
variant: 'default',
|
|
103
|
+
expectedIconCount: 0,
|
|
104
|
+
narrow: false,
|
|
105
|
+
nestedInLiveLog: true,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
key: 'narrow-default',
|
|
109
|
+
storyId: NARROW_STORY,
|
|
110
|
+
text: 'Conversation marked as resolved',
|
|
111
|
+
variant: 'default',
|
|
112
|
+
expectedIconCount: 1,
|
|
113
|
+
narrow: true,
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
key: 'narrow-divider',
|
|
117
|
+
storyId: NARROW_STORY,
|
|
118
|
+
text: 'March 15, 2026',
|
|
119
|
+
variant: 'divider',
|
|
120
|
+
expectedIconCount: 0,
|
|
121
|
+
narrow: true,
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
key: 'long-default',
|
|
125
|
+
storyId: NARROW_STORY,
|
|
126
|
+
text: 'Messages are end-to-end encrypted for everyone in this conversation and on every signed-in device.',
|
|
127
|
+
variant: 'default',
|
|
128
|
+
expectedIconCount: 1,
|
|
129
|
+
narrow: true,
|
|
130
|
+
expectWrap: true,
|
|
131
|
+
},
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
let server: StaticServer;
|
|
135
|
+
let head = '';
|
|
136
|
+
let build = '';
|
|
137
|
+
let browserVersion = 'unknown';
|
|
138
|
+
const receipts: Record<string, unknown>[] = [];
|
|
139
|
+
|
|
140
|
+
function parseRgb(value: string): Rgba | null {
|
|
141
|
+
const channels = value.match(/[\d.]+/g)?.map(Number) ?? [];
|
|
142
|
+
return channels.length >= 3
|
|
143
|
+
? {r: channels[0], g: channels[1], b: channels[2], a: channels[3] ?? 1}
|
|
144
|
+
: null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function composite(foreground: Rgba, background: Rgba): Rgba {
|
|
148
|
+
const alpha = foreground.a + background.a * (1 - foreground.a);
|
|
149
|
+
const channel = (front: number, back: number) =>
|
|
150
|
+
alpha === 0
|
|
151
|
+
? 0
|
|
152
|
+
: (front * foreground.a + back * background.a * (1 - foreground.a)) /
|
|
153
|
+
alpha;
|
|
154
|
+
return {
|
|
155
|
+
r: channel(foreground.r, background.r),
|
|
156
|
+
g: channel(foreground.g, background.g),
|
|
157
|
+
b: channel(foreground.b, background.b),
|
|
158
|
+
a: alpha,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function luminance(color: Rgba): number {
|
|
163
|
+
const linear = [color.r, color.g, color.b].map(channel => {
|
|
164
|
+
const value = channel / 255;
|
|
165
|
+
return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
|
|
166
|
+
});
|
|
167
|
+
return 0.2126 * linear[0] + 0.7152 * linear[1] + 0.0722 * linear[2];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function contrastRatio(foreground: string, background: string): number | null {
|
|
171
|
+
const front = parseRgb(foreground);
|
|
172
|
+
const back = parseRgb(background);
|
|
173
|
+
if (front == null || back == null) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
const resolved = front.a < 1 ? composite(front, back) : front;
|
|
177
|
+
const first = luminance(resolved);
|
|
178
|
+
const second = luminance(back);
|
|
179
|
+
return Number(
|
|
180
|
+
(
|
|
181
|
+
(Math.max(first, second) + 0.05) /
|
|
182
|
+
(Math.min(first, second) + 0.05)
|
|
183
|
+
).toFixed(2),
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function inspectPng(file: string, bytes: Buffer): ImageReceipt {
|
|
188
|
+
const png = PNG.sync.read(bytes);
|
|
189
|
+
const colors = new Set<string>();
|
|
190
|
+
for (let index = 0; index < png.data.length; index += 4) {
|
|
191
|
+
colors.add(
|
|
192
|
+
`${png.data[index]},${png.data[index + 1]},${png.data[index + 2]},${png.data[index + 3]}`,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
file,
|
|
197
|
+
sha256: createHash('sha256').update(bytes).digest('hex'),
|
|
198
|
+
width: png.width,
|
|
199
|
+
height: png.height,
|
|
200
|
+
distinctColors: colors.size,
|
|
201
|
+
nonBlank: colors.size > 1,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function storyUrl(storyId: string, mode: Mode): string {
|
|
206
|
+
return (
|
|
207
|
+
`${server.origin}/iframe.html?id=${storyId}&viewMode=story` +
|
|
208
|
+
`&globals=colorMode:${mode};astryxTheme:neutral;direction:ltr`
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function openCase(
|
|
213
|
+
page: Page,
|
|
214
|
+
auditCase: AuditCase,
|
|
215
|
+
mode: Mode,
|
|
216
|
+
): Promise<{errors: string[]}> {
|
|
217
|
+
const errors: string[] = [];
|
|
218
|
+
page.removeAllListeners('pageerror');
|
|
219
|
+
page.on('pageerror', error => errors.push(String(error)));
|
|
220
|
+
browserVersion = page.context().browser()?.version() ?? 'unknown';
|
|
221
|
+
await page.goto(storyUrl(auditCase.storyId, mode));
|
|
222
|
+
await page.locator(`[data-system-message-case="${auditCase.key}"]`).waitFor();
|
|
223
|
+
await page.waitForFunction(
|
|
224
|
+
expected =>
|
|
225
|
+
document.documentElement.getAttribute('data-theme') === expected,
|
|
226
|
+
mode,
|
|
227
|
+
);
|
|
228
|
+
await holdMotionStill(page);
|
|
229
|
+
await page.evaluate(async () => document.fonts.ready);
|
|
230
|
+
return {errors};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function capture(
|
|
234
|
+
page: Page,
|
|
235
|
+
auditCase: AuditCase,
|
|
236
|
+
mode: Mode,
|
|
237
|
+
errors: string[],
|
|
238
|
+
options: {
|
|
239
|
+
viewport?: {width: number; height: number};
|
|
240
|
+
coarsePointer?: boolean;
|
|
241
|
+
touchPoints?: boolean;
|
|
242
|
+
} = {},
|
|
243
|
+
): Promise<string[]> {
|
|
244
|
+
const viewport = options.viewport ?? {width: 1024, height: 768};
|
|
245
|
+
await page.setViewportSize(viewport);
|
|
246
|
+
const target = page.locator(`[data-system-message-case="${auditCase.key}"]`);
|
|
247
|
+
const actual = await target.evaluate(element => {
|
|
248
|
+
const root = element as HTMLElement;
|
|
249
|
+
const rootStyle = getComputedStyle(root);
|
|
250
|
+
const separator = root.querySelector<HTMLElement>('[role="separator"]');
|
|
251
|
+
const labelId = separator?.getAttribute('aria-labelledby');
|
|
252
|
+
const label = labelId ? document.getElementById(labelId) : null;
|
|
253
|
+
const textElement = label ?? root;
|
|
254
|
+
const content = root.firstElementChild as HTMLElement | null;
|
|
255
|
+
const textStyle = getComputedStyle(textElement);
|
|
256
|
+
const line = separator?.firstElementChild as HTMLElement | null;
|
|
257
|
+
const lineStyle = line ? getComputedStyle(line) : null;
|
|
258
|
+
const rect = root.getBoundingClientRect();
|
|
259
|
+
const contentRect = content?.getBoundingClientRect() ?? null;
|
|
260
|
+
const liveLog = root.closest<HTMLElement>('[role="log"]');
|
|
261
|
+
const textLineTops = new Set<number>();
|
|
262
|
+
const textWalker = document.createTreeWalker(
|
|
263
|
+
textElement,
|
|
264
|
+
NodeFilter.SHOW_TEXT,
|
|
265
|
+
);
|
|
266
|
+
for (
|
|
267
|
+
let textNode = textWalker.nextNode();
|
|
268
|
+
textNode != null;
|
|
269
|
+
textNode = textWalker.nextNode()
|
|
270
|
+
) {
|
|
271
|
+
if ((textNode.textContent ?? '').trim() === '') {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const range = document.createRange();
|
|
275
|
+
range.selectNodeContents(textNode);
|
|
276
|
+
for (const lineRect of range.getClientRects()) {
|
|
277
|
+
if (lineRect.width > 0 && lineRect.height > 0) {
|
|
278
|
+
textLineTops.add(Math.round(lineRect.top * 2) / 2);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const textLineCount = textLineTops.size;
|
|
283
|
+
const parse = (value: string) => {
|
|
284
|
+
const channels = value.match(/[\d.]+/g)?.map(Number) ?? [];
|
|
285
|
+
return channels.length >= 3
|
|
286
|
+
? {r: channels[0], g: channels[1], b: channels[2], a: channels[3] ?? 1}
|
|
287
|
+
: null;
|
|
288
|
+
};
|
|
289
|
+
const candidates: Element[] = [];
|
|
290
|
+
for (
|
|
291
|
+
let current = root.parentElement;
|
|
292
|
+
current != null;
|
|
293
|
+
current = current.parentElement
|
|
294
|
+
) {
|
|
295
|
+
candidates.push(current);
|
|
296
|
+
}
|
|
297
|
+
candidates.push(document.body, document.documentElement);
|
|
298
|
+
const surfaceBackground =
|
|
299
|
+
candidates
|
|
300
|
+
.map(candidate => getComputedStyle(candidate).backgroundColor)
|
|
301
|
+
.find(value => {
|
|
302
|
+
const color = parse(value);
|
|
303
|
+
return color != null && color.a > 0.99;
|
|
304
|
+
}) ?? 'transparent';
|
|
305
|
+
const surface = parse(surfaceBackground);
|
|
306
|
+
const surfaceLuminanceClass =
|
|
307
|
+
surface == null
|
|
308
|
+
? 'unknown'
|
|
309
|
+
: (() => {
|
|
310
|
+
const linear = [surface.r, surface.g, surface.b].map(channel => {
|
|
311
|
+
const value = channel / 255;
|
|
312
|
+
return value <= 0.04045
|
|
313
|
+
? value / 12.92
|
|
314
|
+
: ((value + 0.055) / 1.055) ** 2.4;
|
|
315
|
+
});
|
|
316
|
+
const value =
|
|
317
|
+
0.2126 * linear[0] + 0.7152 * linear[1] + 0.0722 * linear[2];
|
|
318
|
+
return value > 0.5 ? 'light' : 'dark';
|
|
319
|
+
})();
|
|
320
|
+
return {
|
|
321
|
+
storyId: new URL(location.href).searchParams.get('id'),
|
|
322
|
+
selectorCount: document.querySelectorAll(
|
|
323
|
+
`[data-system-message-case="${root.dataset.systemMessageCase}"]`,
|
|
324
|
+
).length,
|
|
325
|
+
visible: rect.width > 0 && rect.height > 0,
|
|
326
|
+
text: (root.textContent ?? '').replace(/\s+/g, ' ').trim(),
|
|
327
|
+
role: root.getAttribute('role'),
|
|
328
|
+
variant: root.getAttribute('data-variant'),
|
|
329
|
+
separatorCount: root.querySelectorAll('[role="separator"]').length,
|
|
330
|
+
separatorOrientation: separator?.getAttribute('aria-orientation') ?? null,
|
|
331
|
+
separatorLabelledTargetText:
|
|
332
|
+
label?.textContent?.replace(/\s+/g, ' ').trim() ?? null,
|
|
333
|
+
iconCount: root.querySelectorAll('svg, [data-slot="icon"]').length,
|
|
334
|
+
interactiveCount: root.querySelectorAll(
|
|
335
|
+
'button, a[href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
|
|
336
|
+
).length,
|
|
337
|
+
direction: rootStyle.direction,
|
|
338
|
+
color: textStyle.color,
|
|
339
|
+
iconColor:
|
|
340
|
+
root.querySelector('svg, [data-slot="icon"]') == null
|
|
341
|
+
? null
|
|
342
|
+
: getComputedStyle(
|
|
343
|
+
root.querySelector('svg, [data-slot="icon"]') as Element,
|
|
344
|
+
).color,
|
|
345
|
+
lineColor: lineStyle?.backgroundColor ?? null,
|
|
346
|
+
fontSize: textStyle.fontSize,
|
|
347
|
+
geometry: {x: rect.x, y: rect.y, width: rect.width, height: rect.height},
|
|
348
|
+
contentGeometry:
|
|
349
|
+
contentRect == null
|
|
350
|
+
? null
|
|
351
|
+
: {
|
|
352
|
+
x: contentRect.x,
|
|
353
|
+
y: contentRect.y,
|
|
354
|
+
width: contentRect.width,
|
|
355
|
+
height: contentRect.height,
|
|
356
|
+
},
|
|
357
|
+
contentStartsWithinRoot:
|
|
358
|
+
contentRect == null || contentRect.left >= rect.left - 1,
|
|
359
|
+
contentEndsWithinRoot:
|
|
360
|
+
contentRect == null || contentRect.right <= rect.right + 1,
|
|
361
|
+
textLineCount,
|
|
362
|
+
liveLog:
|
|
363
|
+
liveLog == null
|
|
364
|
+
? null
|
|
365
|
+
: {
|
|
366
|
+
role: liveLog.getAttribute('role'),
|
|
367
|
+
ariaLive: liveLog.getAttribute('aria-live'),
|
|
368
|
+
},
|
|
369
|
+
overflowFree:
|
|
370
|
+
root.scrollWidth <= root.clientWidth + 1 &&
|
|
371
|
+
document.documentElement.scrollWidth <= innerWidth + 1,
|
|
372
|
+
mode: document.documentElement.getAttribute('data-theme'),
|
|
373
|
+
theme:
|
|
374
|
+
document
|
|
375
|
+
.querySelector('[data-astryx-theme]')
|
|
376
|
+
?.getAttribute('data-astryx-theme') ?? null,
|
|
377
|
+
colorScheme: getComputedStyle(document.documentElement).colorScheme,
|
|
378
|
+
surfaceBackground,
|
|
379
|
+
surfaceLuminanceClass,
|
|
380
|
+
viewport: {width: innerWidth, height: innerHeight},
|
|
381
|
+
devicePixelRatio,
|
|
382
|
+
reducedMotion: matchMedia('(prefers-reduced-motion: reduce)').matches,
|
|
383
|
+
forcedColors: matchMedia('(forced-colors: active)').matches,
|
|
384
|
+
coarsePointer: matchMedia('(pointer: coarse)').matches,
|
|
385
|
+
hoverCapable: matchMedia('(hover: hover)').matches,
|
|
386
|
+
touchPoints: navigator.maxTouchPoints,
|
|
387
|
+
fontsReady: document.fonts.status === 'loaded',
|
|
388
|
+
storyError: [
|
|
389
|
+
...document.querySelectorAll(
|
|
390
|
+
'.sb-errordisplay, [data-testid="story-error"]',
|
|
391
|
+
),
|
|
392
|
+
].some(node => {
|
|
393
|
+
const box = node.getBoundingClientRect();
|
|
394
|
+
const style = getComputedStyle(node);
|
|
395
|
+
return (
|
|
396
|
+
box.width > 0 &&
|
|
397
|
+
box.height > 0 &&
|
|
398
|
+
style.display !== 'none' &&
|
|
399
|
+
style.visibility !== 'hidden' &&
|
|
400
|
+
style.opacity !== '0'
|
|
401
|
+
);
|
|
402
|
+
}),
|
|
403
|
+
};
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
const file = `ChatSystemMessage__${auditCase.key}__neutral-${mode}__ltr.png`;
|
|
407
|
+
const bytes = await target.screenshot({animations: 'disabled'});
|
|
408
|
+
fs.writeFileSync(path.join(OUTPUT, file), bytes);
|
|
409
|
+
const image = inspectPng(file, bytes);
|
|
410
|
+
const textRatio = contrastRatio(actual.color, actual.surfaceBackground);
|
|
411
|
+
const contrastPairs: ContrastPair[] = [
|
|
412
|
+
{
|
|
413
|
+
part: 'text',
|
|
414
|
+
meaningful: true,
|
|
415
|
+
foreground: actual.color,
|
|
416
|
+
backdrop: actual.surfaceBackground,
|
|
417
|
+
ratio: textRatio,
|
|
418
|
+
threshold: 4.5,
|
|
419
|
+
exception: null,
|
|
420
|
+
passed: textRatio != null && textRatio >= 4.5,
|
|
421
|
+
},
|
|
422
|
+
];
|
|
423
|
+
if (actual.iconColor != null) {
|
|
424
|
+
contrastPairs.push({
|
|
425
|
+
part: 'icon',
|
|
426
|
+
meaningful: false,
|
|
427
|
+
foreground: actual.iconColor,
|
|
428
|
+
backdrop: actual.surfaceBackground,
|
|
429
|
+
ratio: contrastRatio(actual.iconColor, actual.surfaceBackground),
|
|
430
|
+
threshold: null,
|
|
431
|
+
exception:
|
|
432
|
+
'The caller-provided icon appears with visible text and is not the only source of meaning.',
|
|
433
|
+
passed: true,
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
if (actual.lineColor != null) {
|
|
437
|
+
contrastPairs.push({
|
|
438
|
+
part: 'divider-line',
|
|
439
|
+
meaningful: false,
|
|
440
|
+
foreground: actual.lineColor,
|
|
441
|
+
backdrop: actual.surfaceBackground,
|
|
442
|
+
ratio: contrastRatio(actual.lineColor, actual.surfaceBackground),
|
|
443
|
+
threshold: null,
|
|
444
|
+
exception:
|
|
445
|
+
'The visible label and labelled separator expose the section boundary without relying on the painted rule.',
|
|
446
|
+
passed: true,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const expected = {
|
|
451
|
+
build: head,
|
|
452
|
+
storyId: auditCase.storyId,
|
|
453
|
+
theme: 'neutral',
|
|
454
|
+
mode,
|
|
455
|
+
direction: 'ltr' as Direction,
|
|
456
|
+
viewport,
|
|
457
|
+
reducedMotion: true,
|
|
458
|
+
forcedColors: false,
|
|
459
|
+
coarsePointer: options.coarsePointer ?? false,
|
|
460
|
+
touchPoints: options.touchPoints ?? false,
|
|
461
|
+
role: 'status',
|
|
462
|
+
variant: auditCase.variant,
|
|
463
|
+
text: auditCase.text,
|
|
464
|
+
separatorCount: auditCase.variant === 'divider' ? 1 : 0,
|
|
465
|
+
separatorOrientation: auditCase.variant === 'divider' ? 'horizontal' : null,
|
|
466
|
+
separatorLabelledTargetText:
|
|
467
|
+
auditCase.variant === 'divider' ? auditCase.text : null,
|
|
468
|
+
iconCount: auditCase.expectedIconCount,
|
|
469
|
+
interactiveCount: 0,
|
|
470
|
+
expectWrap: auditCase.expectWrap ?? false,
|
|
471
|
+
textLineExpectation: auditCase.expectWrap ? 'multiple' : 'single',
|
|
472
|
+
liveLog: auditCase.nestedInLiveLog
|
|
473
|
+
? {role: 'log', ariaLive: 'polite'}
|
|
474
|
+
: null,
|
|
475
|
+
};
|
|
476
|
+
const failures: string[] = [];
|
|
477
|
+
const check = (condition: boolean, message: string) => {
|
|
478
|
+
if (!condition) {
|
|
479
|
+
failures.push(message);
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
const textLineCountMatches = expected.expectWrap
|
|
484
|
+
? actual.textLineCount >= 2
|
|
485
|
+
: actual.textLineCount === 1;
|
|
486
|
+
|
|
487
|
+
check(actual.storyId === expected.storyId, 'Storybook story id drifted');
|
|
488
|
+
check(actual.selectorCount === 1, 'expected exactly one audit subject');
|
|
489
|
+
check(actual.visible, 'audit subject has no visible geometry');
|
|
490
|
+
check(actual.text === expected.text, 'visible content drifted');
|
|
491
|
+
check(actual.role === expected.role, 'status role drifted');
|
|
492
|
+
check(actual.variant === expected.variant, 'variant reflection drifted');
|
|
493
|
+
check(
|
|
494
|
+
actual.separatorCount === expected.separatorCount,
|
|
495
|
+
'separator count drifted',
|
|
496
|
+
);
|
|
497
|
+
check(
|
|
498
|
+
actual.separatorOrientation === expected.separatorOrientation,
|
|
499
|
+
'separator orientation drifted',
|
|
500
|
+
);
|
|
501
|
+
check(
|
|
502
|
+
actual.separatorLabelledTargetText === expected.separatorLabelledTargetText,
|
|
503
|
+
'separator labelled target text drifted',
|
|
504
|
+
);
|
|
505
|
+
if (expected.iconCount != null) {
|
|
506
|
+
check(actual.iconCount === expected.iconCount, 'icon presence drifted');
|
|
507
|
+
}
|
|
508
|
+
check(
|
|
509
|
+
actual.interactiveCount === expected.interactiveCount,
|
|
510
|
+
'component unexpectedly owns an interactive target',
|
|
511
|
+
);
|
|
512
|
+
check(actual.direction === expected.direction, 'computed direction drifted');
|
|
513
|
+
check(actual.overflowFree, 'component or page has horizontal overflow');
|
|
514
|
+
check(
|
|
515
|
+
textLineCountMatches,
|
|
516
|
+
`text line count ${actual.textLineCount} does not match the ${expected.textLineExpectation} expectation`,
|
|
517
|
+
);
|
|
518
|
+
if (expected.expectWrap) {
|
|
519
|
+
check(
|
|
520
|
+
actual.contentStartsWithinRoot,
|
|
521
|
+
'long content crosses the root start edge',
|
|
522
|
+
);
|
|
523
|
+
check(
|
|
524
|
+
actual.contentEndsWithinRoot,
|
|
525
|
+
'long content crosses the root end edge',
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
check(
|
|
529
|
+
JSON.stringify(actual.liveLog) === JSON.stringify(expected.liveLog),
|
|
530
|
+
'live-log composition drifted',
|
|
531
|
+
);
|
|
532
|
+
check(actual.theme === expected.theme, 'neutral theme did not settle');
|
|
533
|
+
check(actual.mode === expected.mode, 'color mode did not settle');
|
|
534
|
+
check(actual.colorScheme.includes(mode), 'computed color-scheme drifted');
|
|
535
|
+
check(
|
|
536
|
+
actual.surfaceLuminanceClass === mode,
|
|
537
|
+
'surface luminance does not match color mode',
|
|
538
|
+
);
|
|
539
|
+
check(
|
|
540
|
+
actual.viewport.width === viewport.width &&
|
|
541
|
+
actual.viewport.height === viewport.height,
|
|
542
|
+
'viewport drifted',
|
|
543
|
+
);
|
|
544
|
+
check(actual.devicePixelRatio === 1, 'device pixel ratio drifted');
|
|
545
|
+
check(actual.reducedMotion, 'motion was not held still');
|
|
546
|
+
check(!actual.forcedColors, 'forced-colors unexpectedly active');
|
|
547
|
+
check(
|
|
548
|
+
actual.coarsePointer === expected.coarsePointer,
|
|
549
|
+
'pointer modality drifted',
|
|
550
|
+
);
|
|
551
|
+
if (expected.touchPoints) {
|
|
552
|
+
check(actual.touchPoints > 0, 'coarse-pointer context has no touch points');
|
|
553
|
+
}
|
|
554
|
+
check(actual.fontsReady, 'fonts did not settle');
|
|
555
|
+
check(!actual.storyError, 'Storybook rendered an error surface');
|
|
556
|
+
check(errors.length === 0, 'page emitted an error');
|
|
557
|
+
check(image.nonBlank, 'subject crop is blank');
|
|
558
|
+
for (const pair of contrastPairs) {
|
|
559
|
+
check(pair.passed, `${pair.part} contrast pair failed`);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const sensorRows = {
|
|
563
|
+
Build: {
|
|
564
|
+
expected: {headSha: head},
|
|
565
|
+
observed: {checkoutSha: head, storybookSha: build},
|
|
566
|
+
passed: build === head,
|
|
567
|
+
},
|
|
568
|
+
Story: {
|
|
569
|
+
expected: {id: expected.storyId, case: auditCase.key},
|
|
570
|
+
observed: {id: actual.storyId, selectorCount: actual.selectorCount},
|
|
571
|
+
passed: actual.storyId === expected.storyId && actual.selectorCount === 1,
|
|
572
|
+
},
|
|
573
|
+
Theme: {
|
|
574
|
+
expected: expected.theme,
|
|
575
|
+
observed: actual.theme,
|
|
576
|
+
passed: actual.theme === expected.theme,
|
|
577
|
+
},
|
|
578
|
+
'Color mode': {
|
|
579
|
+
expected: {
|
|
580
|
+
mode,
|
|
581
|
+
colorScheme: mode,
|
|
582
|
+
surfaceLuminanceClass: mode,
|
|
583
|
+
},
|
|
584
|
+
observed: {
|
|
585
|
+
mode: actual.mode,
|
|
586
|
+
colorScheme: actual.colorScheme,
|
|
587
|
+
surfaceBackground: actual.surfaceBackground,
|
|
588
|
+
surfaceLuminanceClass: actual.surfaceLuminanceClass,
|
|
589
|
+
},
|
|
590
|
+
passed:
|
|
591
|
+
actual.mode === mode &&
|
|
592
|
+
actual.colorScheme.includes(mode) &&
|
|
593
|
+
actual.surfaceLuminanceClass === mode,
|
|
594
|
+
},
|
|
595
|
+
Direction: {
|
|
596
|
+
expected: expected.direction,
|
|
597
|
+
observed: actual.direction,
|
|
598
|
+
passed: actual.direction === expected.direction,
|
|
599
|
+
},
|
|
600
|
+
'Viewport/media': {
|
|
601
|
+
expected: {
|
|
602
|
+
viewport,
|
|
603
|
+
devicePixelRatio: 1,
|
|
604
|
+
reducedMotion: true,
|
|
605
|
+
forcedColors: false,
|
|
606
|
+
coarsePointer: expected.coarsePointer,
|
|
607
|
+
},
|
|
608
|
+
observed: {
|
|
609
|
+
viewport: actual.viewport,
|
|
610
|
+
devicePixelRatio: actual.devicePixelRatio,
|
|
611
|
+
reducedMotion: actual.reducedMotion,
|
|
612
|
+
forcedColors: actual.forcedColors,
|
|
613
|
+
coarsePointer: actual.coarsePointer,
|
|
614
|
+
hoverCapable: actual.hoverCapable,
|
|
615
|
+
touchPoints: actual.touchPoints,
|
|
616
|
+
},
|
|
617
|
+
passed:
|
|
618
|
+
actual.viewport.width === viewport.width &&
|
|
619
|
+
actual.viewport.height === viewport.height &&
|
|
620
|
+
actual.devicePixelRatio === 1 &&
|
|
621
|
+
actual.reducedMotion &&
|
|
622
|
+
!actual.forcedColors &&
|
|
623
|
+
actual.coarsePointer === expected.coarsePointer &&
|
|
624
|
+
(!expected.touchPoints || actual.touchPoints > 0),
|
|
625
|
+
},
|
|
626
|
+
'Rendered state': {
|
|
627
|
+
expected: {
|
|
628
|
+
role: expected.role,
|
|
629
|
+
variant: expected.variant,
|
|
630
|
+
text: expected.text,
|
|
631
|
+
separatorCount: expected.separatorCount,
|
|
632
|
+
separatorOrientation: expected.separatorOrientation,
|
|
633
|
+
separatorLabelledTargetText: expected.separatorLabelledTargetText,
|
|
634
|
+
iconCount: expected.iconCount,
|
|
635
|
+
interactiveCount: 0,
|
|
636
|
+
liveLog: expected.liveLog,
|
|
637
|
+
},
|
|
638
|
+
observed: {
|
|
639
|
+
role: actual.role,
|
|
640
|
+
variant: actual.variant,
|
|
641
|
+
text: actual.text,
|
|
642
|
+
separatorCount: actual.separatorCount,
|
|
643
|
+
separatorOrientation: actual.separatorOrientation,
|
|
644
|
+
separatorLabelledTargetText: actual.separatorLabelledTargetText,
|
|
645
|
+
iconCount: actual.iconCount,
|
|
646
|
+
interactiveCount: actual.interactiveCount,
|
|
647
|
+
liveLog: actual.liveLog,
|
|
648
|
+
dividerIconContract:
|
|
649
|
+
auditCase.key === 'divider-icon' ? 'unresolved' : null,
|
|
650
|
+
contrastPairs,
|
|
651
|
+
},
|
|
652
|
+
passed:
|
|
653
|
+
actual.role === expected.role &&
|
|
654
|
+
actual.variant === expected.variant &&
|
|
655
|
+
actual.text === expected.text &&
|
|
656
|
+
actual.separatorCount === expected.separatorCount &&
|
|
657
|
+
actual.separatorOrientation === expected.separatorOrientation &&
|
|
658
|
+
actual.separatorLabelledTargetText ===
|
|
659
|
+
expected.separatorLabelledTargetText &&
|
|
660
|
+
(expected.iconCount == null ||
|
|
661
|
+
actual.iconCount === expected.iconCount) &&
|
|
662
|
+
actual.interactiveCount === 0 &&
|
|
663
|
+
JSON.stringify(actual.liveLog) === JSON.stringify(expected.liveLog) &&
|
|
664
|
+
contrastPairs.every(pair => pair.passed),
|
|
665
|
+
},
|
|
666
|
+
'Subject geometry': {
|
|
667
|
+
expected: {
|
|
668
|
+
visible: true,
|
|
669
|
+
overflowFree: true,
|
|
670
|
+
nonZero: true,
|
|
671
|
+
wraps: expected.expectWrap,
|
|
672
|
+
textLineExpectation: expected.textLineExpectation,
|
|
673
|
+
insideInlineEdges: expected.expectWrap,
|
|
674
|
+
},
|
|
675
|
+
observed: {
|
|
676
|
+
...actual.geometry,
|
|
677
|
+
contentGeometry: actual.contentGeometry,
|
|
678
|
+
contentStartsWithinRoot: actual.contentStartsWithinRoot,
|
|
679
|
+
contentEndsWithinRoot: actual.contentEndsWithinRoot,
|
|
680
|
+
textLineCount: actual.textLineCount,
|
|
681
|
+
overflowFree: actual.overflowFree,
|
|
682
|
+
},
|
|
683
|
+
passed:
|
|
684
|
+
actual.visible &&
|
|
685
|
+
actual.geometry.width > 0 &&
|
|
686
|
+
actual.geometry.height > 0 &&
|
|
687
|
+
actual.overflowFree &&
|
|
688
|
+
textLineCountMatches &&
|
|
689
|
+
(!expected.expectWrap ||
|
|
690
|
+
(actual.contentStartsWithinRoot && actual.contentEndsWithinRoot)),
|
|
691
|
+
},
|
|
692
|
+
'Settled render': {
|
|
693
|
+
expected: {fontsReady: true, pageErrors: 0, storyError: false},
|
|
694
|
+
observed: {
|
|
695
|
+
fontsReady: actual.fontsReady,
|
|
696
|
+
pageErrors: errors,
|
|
697
|
+
storyError: actual.storyError,
|
|
698
|
+
},
|
|
699
|
+
passed: actual.fontsReady && errors.length === 0 && !actual.storyError,
|
|
700
|
+
},
|
|
701
|
+
Image: {
|
|
702
|
+
expected: {nonBlank: true, nonZeroPixels: true},
|
|
703
|
+
observed: image,
|
|
704
|
+
passed: image.nonBlank && image.width > 0 && image.height > 0,
|
|
705
|
+
},
|
|
706
|
+
};
|
|
707
|
+
const receipt = {
|
|
708
|
+
frame: `${auditCase.key}__neutral-${mode}__ltr`,
|
|
709
|
+
passed: failures.length === 0,
|
|
710
|
+
failures,
|
|
711
|
+
expected,
|
|
712
|
+
observed: actual,
|
|
713
|
+
contrastPairs,
|
|
714
|
+
sensors: sensorRows,
|
|
715
|
+
image,
|
|
716
|
+
};
|
|
717
|
+
fs.writeFileSync(
|
|
718
|
+
path.join(OUTPUT, `${file}.sensors.json`),
|
|
719
|
+
`${JSON.stringify(receipt, null, 2)}\n`,
|
|
720
|
+
);
|
|
721
|
+
receipts.push(receipt);
|
|
722
|
+
return failures.map(detail => `${auditCase.key}/${mode}: ${detail}`);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
async function writeContactSheet(page: Page): Promise<ImageReceipt> {
|
|
726
|
+
const figures = receipts
|
|
727
|
+
.map(receipt => {
|
|
728
|
+
const image = receipt.image as ImageReceipt;
|
|
729
|
+
const bytes = fs.readFileSync(path.join(OUTPUT, image.file));
|
|
730
|
+
return `<figure><img src="data:image/png;base64,${bytes.toString('base64')}" alt=""><figcaption>${String(receipt.frame)}</figcaption></figure>`;
|
|
731
|
+
})
|
|
732
|
+
.join('');
|
|
733
|
+
await page.setViewportSize({width: 1200, height: 900});
|
|
734
|
+
await page.setContent(`<!doctype html>
|
|
735
|
+
<style>
|
|
736
|
+
html { color-scheme: light; background: #f3f4f6; }
|
|
737
|
+
body { margin: 20px; font: 12px/1.35 system-ui, sans-serif; color: #111827; }
|
|
738
|
+
h1 { margin: 0 0 16px; font-size: 20px; }
|
|
739
|
+
main { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
|
|
740
|
+
figure { margin: 0; padding: 10px; border: 1px solid #d1d5db; border-radius: 6px; background: white; }
|
|
741
|
+
img { display: block; width: 100%; height: 96px; object-fit: contain; }
|
|
742
|
+
figcaption { margin-top: 8px; overflow-wrap: anywhere; }
|
|
743
|
+
</style>
|
|
744
|
+
<h1>ChatSystemMessage exact-head state evidence</h1>
|
|
745
|
+
<main>${figures}</main>`);
|
|
746
|
+
await page.locator('img').evaluateAll(async images => {
|
|
747
|
+
await Promise.all(
|
|
748
|
+
images.map(async image => {
|
|
749
|
+
const candidate = image as HTMLImageElement;
|
|
750
|
+
if (candidate.complete) {
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
await new Promise<void>((resolve, reject) => {
|
|
754
|
+
candidate.addEventListener('load', () => resolve(), {once: true});
|
|
755
|
+
candidate.addEventListener(
|
|
756
|
+
'error',
|
|
757
|
+
() => reject(new Error('contact-sheet image failed')),
|
|
758
|
+
{once: true},
|
|
759
|
+
);
|
|
760
|
+
});
|
|
761
|
+
}),
|
|
762
|
+
);
|
|
763
|
+
});
|
|
764
|
+
const file = 'ChatSystemMessage__contact-sheet.png';
|
|
765
|
+
const bytes = await page.screenshot({fullPage: true, animations: 'disabled'});
|
|
766
|
+
fs.writeFileSync(path.join(OUTPUT, file), bytes);
|
|
767
|
+
return inspectPng(file, bytes);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
async function mobilePage(
|
|
771
|
+
browser: Browser,
|
|
772
|
+
): Promise<{page: Page; close: () => Promise<void>}> {
|
|
773
|
+
const context = await browser.newContext({
|
|
774
|
+
viewport: {width: 320, height: 640},
|
|
775
|
+
deviceScaleFactor: 1,
|
|
776
|
+
hasTouch: true,
|
|
777
|
+
isMobile: true,
|
|
778
|
+
reducedMotion: 'reduce',
|
|
779
|
+
});
|
|
780
|
+
return {
|
|
781
|
+
page: await context.newPage(),
|
|
782
|
+
close: async () => {
|
|
783
|
+
await context.close();
|
|
784
|
+
},
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
test.describe.configure({mode: 'serial', retries: 0});
|
|
789
|
+
|
|
790
|
+
test.beforeAll(async () => {
|
|
791
|
+
head = execFileSync('git', ['rev-parse', 'HEAD'], {encoding: 'utf8'}).trim();
|
|
792
|
+
const dirtyTracked = execFileSync(
|
|
793
|
+
'git',
|
|
794
|
+
['status', '--porcelain', '--untracked-files=no'],
|
|
795
|
+
{encoding: 'utf8'},
|
|
796
|
+
).trim();
|
|
797
|
+
const dirtyEvidenceFiles = execFileSync(
|
|
798
|
+
'git',
|
|
799
|
+
[
|
|
800
|
+
'status',
|
|
801
|
+
'--porcelain',
|
|
802
|
+
'--untracked-files=all',
|
|
803
|
+
'--',
|
|
804
|
+
'apps/storybook/stories/ChatSystemMessage.stories.tsx',
|
|
805
|
+
'apps/storybook/stories/ChatSystemMessageAudit.stories.tsx',
|
|
806
|
+
'packages/core/src/Chat/__tests__/ChatSystemMessage.a11y.chromium.spec.ts',
|
|
807
|
+
],
|
|
808
|
+
{encoding: 'utf8'},
|
|
809
|
+
).trim();
|
|
810
|
+
const dirty = [dirtyTracked, dirtyEvidenceFiles].filter(Boolean).join('\n');
|
|
811
|
+
if (dirty !== '') {
|
|
812
|
+
throw new Error(
|
|
813
|
+
`browser evidence requires a clean committed source set: ${dirty}`,
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
if (process.env.ASTRYX_HEAD_SHA && process.env.ASTRYX_HEAD_SHA !== head) {
|
|
817
|
+
throw new Error('PR head differs from the checked out source');
|
|
818
|
+
}
|
|
819
|
+
fs.rmSync(OUTPUT, {recursive: true, force: true});
|
|
820
|
+
fs.mkdirSync(OUTPUT, {recursive: true});
|
|
821
|
+
server = await serveStorybook(
|
|
822
|
+
process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
|
|
823
|
+
);
|
|
824
|
+
const stamp = await fetch(`${server.origin}/astryx-build-sha.txt`);
|
|
825
|
+
if (!stamp.ok) {
|
|
826
|
+
throw new Error('Storybook build is missing its source stamp');
|
|
827
|
+
}
|
|
828
|
+
build = (await stamp.text()).trim();
|
|
829
|
+
if (build !== head) {
|
|
830
|
+
throw new Error('Storybook bytes differ from the PR head');
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
test.afterAll(async () => {
|
|
835
|
+
const expectedFrames = CASES.flatMap(auditCase =>
|
|
836
|
+
(['light', 'dark'] as const).map(
|
|
837
|
+
mode => `${auditCase.key}__neutral-${mode}__ltr`,
|
|
838
|
+
),
|
|
839
|
+
);
|
|
840
|
+
const observedFrames = new Set(
|
|
841
|
+
receipts.map(receipt => String(receipt.frame)),
|
|
842
|
+
);
|
|
843
|
+
const d7ContrastPairs = receipts.flatMap(receipt =>
|
|
844
|
+
(receipt.contrastPairs as ContrastPair[]).map(pair => ({
|
|
845
|
+
frame: receipt.frame,
|
|
846
|
+
...pair,
|
|
847
|
+
})),
|
|
848
|
+
);
|
|
849
|
+
const matrixFailures = [
|
|
850
|
+
...expectedFrames
|
|
851
|
+
.filter(frame => !observedFrames.has(frame))
|
|
852
|
+
.map(frame => `missing frame: ${frame}`),
|
|
853
|
+
...receipts
|
|
854
|
+
.filter(receipt => receipt.passed !== true)
|
|
855
|
+
.map(receipt => `failed frame: ${String(receipt.frame)}`),
|
|
856
|
+
...d7ContrastPairs
|
|
857
|
+
.filter(pair => !pair.passed)
|
|
858
|
+
.map(pair => `failed D7 pair: ${String(pair.frame)}/${pair.part}`),
|
|
859
|
+
];
|
|
860
|
+
const contactSheetFile = 'ChatSystemMessage__contact-sheet.png';
|
|
861
|
+
const contactSheetPath = path.join(OUTPUT, contactSheetFile);
|
|
862
|
+
const contactSheet = fs.existsSync(contactSheetPath)
|
|
863
|
+
? inspectPng(contactSheetFile, fs.readFileSync(contactSheetPath))
|
|
864
|
+
: null;
|
|
865
|
+
if (contactSheet?.nonBlank !== true) {
|
|
866
|
+
matrixFailures.push('missing or blank contact sheet');
|
|
867
|
+
}
|
|
868
|
+
if (head && build) {
|
|
869
|
+
fs.writeFileSync(
|
|
870
|
+
path.join(OUTPUT, 'manifest.json'),
|
|
871
|
+
`${JSON.stringify(
|
|
872
|
+
{
|
|
873
|
+
version: 1,
|
|
874
|
+
component: 'core/ChatSystemMessage',
|
|
875
|
+
headSha: head,
|
|
876
|
+
storybookSha: build,
|
|
877
|
+
browser: browserVersion,
|
|
878
|
+
failClosed: true,
|
|
879
|
+
matrixComplete: matrixFailures.length === 0,
|
|
880
|
+
matrixFailures,
|
|
881
|
+
sensorCount: 10,
|
|
882
|
+
requiredSensors: [
|
|
883
|
+
'Build',
|
|
884
|
+
'Story',
|
|
885
|
+
'Theme',
|
|
886
|
+
'Color mode',
|
|
887
|
+
'Direction',
|
|
888
|
+
'Viewport/media',
|
|
889
|
+
'Rendered state',
|
|
890
|
+
'Subject geometry',
|
|
891
|
+
'Settled render',
|
|
892
|
+
'Image',
|
|
893
|
+
],
|
|
894
|
+
visualEvidence: {
|
|
895
|
+
requiredPair: true,
|
|
896
|
+
reason:
|
|
897
|
+
'The production wrapping repair changes long-content pixels. The PR report links the failing-before and fixed-after exact-head artifacts and receipts.',
|
|
898
|
+
subjectiveAcceptanceClaimed: false,
|
|
899
|
+
contactSheet,
|
|
900
|
+
},
|
|
901
|
+
unresolved: {
|
|
902
|
+
dividerIcon:
|
|
903
|
+
'The public icon input is supplied to the divider fixture and its observed output is recorded without asserting either candidate contract.',
|
|
904
|
+
},
|
|
905
|
+
notApplicable: {
|
|
906
|
+
hover: 'ChatSystemMessage has no component-owned interaction.',
|
|
907
|
+
focus:
|
|
908
|
+
'ChatSystemMessage introduces no focusable element or focus-visible state.',
|
|
909
|
+
pressed: 'ChatSystemMessage has no activation behavior.',
|
|
910
|
+
disabled: 'ChatSystemMessage exposes no disabled state.',
|
|
911
|
+
loading: 'ChatSystemMessage exposes no loading state.',
|
|
912
|
+
selected: 'ChatSystemMessage exposes no selection state.',
|
|
913
|
+
keyboard: 'ChatSystemMessage owns no keyboard interaction.',
|
|
914
|
+
pointerTarget: 'ChatSystemMessage owns no pointer target.',
|
|
915
|
+
},
|
|
916
|
+
emptyContentPartitions: {
|
|
917
|
+
emptyString:
|
|
918
|
+
'Focused unit evidence: the default variant keeps role=status and renders the content unchanged (empty). In the divider variant, the status root and separator remain; no visible label or accessible name is produced.',
|
|
919
|
+
numericZero:
|
|
920
|
+
'Focused unit evidence: the default variant keeps role=status and renders the content unchanged as one 0. In the divider variant, the status root and separator remain; Divider paints two zero text nodes (textContent="00") with no accessible name.',
|
|
921
|
+
emptyFragment:
|
|
922
|
+
'Focused unit evidence: the default variant keeps role=status and renders the content unchanged (empty). In the divider variant, the status root and separator remain; no visible label or accessible name is produced.',
|
|
923
|
+
},
|
|
924
|
+
sharedAdvisories: {
|
|
925
|
+
dividerFalseyLabel:
|
|
926
|
+
'Under spec:AST-002/FR15, empty string and empty Fragment content leave the separator without a visible label or accessible name, while numeric zero paints stray textContent="00" with no accessible name. This shared advisory is routed to component:Divider.',
|
|
927
|
+
dividerLongLabelOverflow:
|
|
928
|
+
'Source inspection shows that a long divider label can cross the inline edge because Divider owns a non-shrinking label. This is routed to component:Divider without assigning the defect to ChatSystemMessage.',
|
|
929
|
+
dividerForcedColors:
|
|
930
|
+
'The Divider rule may disappear in forced colors; this audit does not exercise that mode and routes the advisory to component:Divider.',
|
|
931
|
+
},
|
|
932
|
+
unverified: {
|
|
933
|
+
forcedColors:
|
|
934
|
+
'Forced-colors rendering was not exercised. The default row makes no support claim, and the disappearing Divider rule is a shared component:Divider advisory.',
|
|
935
|
+
assistiveTechnology:
|
|
936
|
+
'Browser role exposure is measured, but spoken announcement timing and output were not verified with real assistive technology.',
|
|
937
|
+
nestedLiveRegions:
|
|
938
|
+
'The nested-log frame records role=status inside ChatMessageList role=log aria-live=polite, but double-announcement behavior is unverified under spec:AST-009/FR4 and FR10.',
|
|
939
|
+
},
|
|
940
|
+
stateVisualConformance: {
|
|
941
|
+
default: ['text', 'text-with-icon', 'wrapped-long-content'],
|
|
942
|
+
divider: [
|
|
943
|
+
'labelled-separator',
|
|
944
|
+
'unresolved-divider-icon-observation',
|
|
945
|
+
],
|
|
946
|
+
nestedLiveLog: ['status-inside-polite-log-unverified'],
|
|
947
|
+
colorModes: ['light', 'dark'],
|
|
948
|
+
narrowCoarsePointer: [
|
|
949
|
+
'default-with-icon',
|
|
950
|
+
'divider',
|
|
951
|
+
'wrapped-long-content',
|
|
952
|
+
],
|
|
953
|
+
},
|
|
954
|
+
frames: receipts,
|
|
955
|
+
d7ContrastPairs,
|
|
956
|
+
},
|
|
957
|
+
null,
|
|
958
|
+
2,
|
|
959
|
+
)}\n`,
|
|
960
|
+
);
|
|
961
|
+
}
|
|
962
|
+
await server?.close();
|
|
963
|
+
expect(matrixFailures, 'complete exact-head evidence matrix').toEqual([]);
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
test('captures the complete neutral light and dark state matrix', async ({
|
|
967
|
+
page,
|
|
968
|
+
}) => {
|
|
969
|
+
await page.emulateMedia({reducedMotion: 'reduce'});
|
|
970
|
+
const failures: string[] = [];
|
|
971
|
+
for (const mode of ['light', 'dark'] as const) {
|
|
972
|
+
for (const auditCase of CASES.filter(item => !item.narrow)) {
|
|
973
|
+
const {errors} = await openCase(page, auditCase, mode);
|
|
974
|
+
failures.push(...(await capture(page, auditCase, mode, errors)));
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
expect(failures).toEqual([]);
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
test('captures 320px coarse-pointer reflow and the contact sheet', async ({
|
|
981
|
+
browser,
|
|
982
|
+
}) => {
|
|
983
|
+
const mobile = await mobilePage(browser);
|
|
984
|
+
try {
|
|
985
|
+
const failures: string[] = [];
|
|
986
|
+
for (const mode of ['light', 'dark'] as const) {
|
|
987
|
+
for (const auditCase of CASES.filter(item => item.narrow)) {
|
|
988
|
+
const {errors} = await openCase(mobile.page, auditCase, mode);
|
|
989
|
+
failures.push(
|
|
990
|
+
...(await capture(mobile.page, auditCase, mode, errors, {
|
|
991
|
+
viewport: {width: 320, height: 640},
|
|
992
|
+
coarsePointer: true,
|
|
993
|
+
touchPoints: true,
|
|
994
|
+
})),
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
const contactSheet = await writeContactSheet(mobile.page);
|
|
999
|
+
expect(contactSheet.nonBlank).toBe(true);
|
|
1000
|
+
expect(failures).toEqual([]);
|
|
1001
|
+
} finally {
|
|
1002
|
+
await mobile.close();
|
|
1003
|
+
}
|
|
1004
|
+
});
|