|
|
@@ -0,0 +1,95 @@
|
|
|
+/**
|
|
|
+ * The chat flow's font-size-axis adoption as CSS text. jsdom has no layout,
|
|
|
+ * so these read the declarations that make think text, compaction rows, the
|
|
|
+ * message clock, and the icon-action buttons follow the Settings font-size
|
|
|
+ * preference through --dsh-content-font-size / --dsh-content-font-delta.
|
|
|
+ */
|
|
|
+import { readFileSync } from 'node:fs'
|
|
|
+import { fileURLToPath } from 'node:url'
|
|
|
+import { describe, expect, it } from 'vitest'
|
|
|
+
|
|
|
+const read = (name: string): string =>
|
|
|
+ readFileSync(fileURLToPath(new URL(`../src/client/chat/${name}`, import.meta.url)), 'utf8')
|
|
|
+
|
|
|
+function declarationsFrom(source: string, selector: string): string[] {
|
|
|
+ const declarationText = source.replace(/\/\*[\s\S]*?\*\//g, ' ')
|
|
|
+ const rule = new RegExp(`(?:^|\\})\\s*${selector.replace(/[.[\]():*+^$\\]/g, '\\$&')}\\s*\\{([^{}]*)\\}`).exec(declarationText)
|
|
|
+ if (rule === null) throw new Error(`no \`${selector}\` rule`)
|
|
|
+ return (rule[1] ?? '').split(';').map(part => part.trim()).filter(Boolean)
|
|
|
+}
|
|
|
+
|
|
|
+describe('chat flow font-size axis', () => {
|
|
|
+ it('think text keeps its 2px step under the body size and moves with the delta', () => {
|
|
|
+ const css = read('ReasoningRow.module.css')
|
|
|
+ for (const selector of ['.summary', '.thinkBody']) {
|
|
|
+ expect(declarationsFrom(css, selector)).toEqual(expect.arrayContaining([
|
|
|
+ 'font-size: calc(12px + var(--dsh-content-font-delta, 0px))',
|
|
|
+ 'line-height: calc(20px + var(--dsh-content-font-delta, 0px))',
|
|
|
+ ]))
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ it('command and context summaries ride the axis at the body size', () => {
|
|
|
+ expect(declarationsFrom(read('GenericCommandCard.module.css'), '.summary')).toEqual(expect.arrayContaining([
|
|
|
+ 'font-size: var(--dsh-content-font-size, 14px)',
|
|
|
+ 'line-height: calc(24px + var(--dsh-content-font-delta, 0px))',
|
|
|
+ ]))
|
|
|
+ const context = read('ContextInjectionRow.module.css')
|
|
|
+ for (const selector of ['.source', '.summary']) {
|
|
|
+ expect(declarationsFrom(context, selector)).toEqual(expect.arrayContaining([
|
|
|
+ 'font-size: var(--dsh-content-font-size, 14px)',
|
|
|
+ 'line-height: calc(24px + var(--dsh-content-font-delta, 0px))',
|
|
|
+ ]))
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ it('the message clock and action glyphs scale with the text they serve', () => {
|
|
|
+ const actions = read('MessageIconActions.module.css')
|
|
|
+ for (const selector of ['.timeStart', '.timeEnd']) {
|
|
|
+ expect(declarationsFrom(actions, selector)).toEqual(expect.arrayContaining([
|
|
|
+ 'font-size: var(--dsh-content-font-size, 14px)',
|
|
|
+ ]))
|
|
|
+ }
|
|
|
+ expect(declarationsFrom(actions, '.action svg')).toEqual(expect.arrayContaining([
|
|
|
+ 'width: calc(16px + var(--dsh-content-font-delta, 0px))',
|
|
|
+ 'height: calc(16px + var(--dsh-content-font-delta, 0px))',
|
|
|
+ ]))
|
|
|
+ })
|
|
|
+
|
|
|
+ it('compaction rows follow the axis like the disclosure rows they mirror', () => {
|
|
|
+ const css = read('MessageItem.module.css')
|
|
|
+ for (const selector of ['.compactionTitle', '.compactionSummary', '.compactionBody']) {
|
|
|
+ expect(declarationsFrom(css, selector)).toEqual(expect.arrayContaining([
|
|
|
+ 'font-size: var(--dsh-content-font-size, 14px)',
|
|
|
+ 'line-height: calc(24px + var(--dsh-content-font-delta, 0px))',
|
|
|
+ ]))
|
|
|
+ }
|
|
|
+ expect(declarationsFrom(css, '.compactionLeading svg')).toEqual(expect.arrayContaining([
|
|
|
+ 'width: calc(14px + var(--dsh-content-font-delta, 0px))',
|
|
|
+ 'height: calc(14px + var(--dsh-content-font-delta, 0px))',
|
|
|
+ ]))
|
|
|
+ })
|
|
|
+
|
|
|
+ it('expanded bodies indent by 22px + delta so content stays under the shifted title start', () => {
|
|
|
+ // The DisclosureRow title starts at leading (16 + delta) + gap 6; a fixed
|
|
|
+ // 22px indent would misalign at every non-default size.
|
|
|
+ const indent = 'calc(22px + var(--dsh-content-font-delta, 0px))'
|
|
|
+ expect(declarationsFrom(read('ReasoningRow.module.css'), '.thinkBody'))
|
|
|
+ .toEqual(expect.arrayContaining([`padding: 4px 0 4px ${indent}`]))
|
|
|
+ expect(declarationsFrom(read('MessageItem.module.css'), '.compactionBody'))
|
|
|
+ .toEqual(expect.arrayContaining([`padding: 4px 0 4px ${indent}`]))
|
|
|
+ expect(declarationsFrom(read('ContextInjectionRow.module.css'), '.body'))
|
|
|
+ .toEqual(expect.arrayContaining([`margin: 4px 0 0 ${indent}`]))
|
|
|
+ expect(declarationsFrom(read('TurnUsageDisclosure.module.css'), '.details'))
|
|
|
+ .toEqual(expect.arrayContaining([`margin: 4px 0 0 ${indent}`]))
|
|
|
+ })
|
|
|
+
|
|
|
+ it('the interrupted-turn tag stays fixed like the dense token variants', () => {
|
|
|
+ // 11px would fall to an illegible 9px at the 12px floor; the tag is
|
|
|
+ // exempt from the axis the same way small/code tokens are.
|
|
|
+ expect(declarationsFrom(read('AssistantMarkdown.module.css'), '.stopped')).toEqual(expect.arrayContaining([
|
|
|
+ 'font-size: 11px',
|
|
|
+ 'line-height: 18px',
|
|
|
+ ]))
|
|
|
+ })
|
|
|
+})
|