Compare commits
3
Commits
ac6963279c
...
9218521dcb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9218521dcb | ||
|
|
43875b991b | ||
|
|
8aa002b485 |
@@ -2,6 +2,17 @@
|
|||||||
|
|
||||||
版本说明按倒序排列。提交信息遵循 [CONTRIBUTING.md](CONTRIBUTING.md) 的单行规范,因此每次改动"为什么这样改、影响面是什么"记在这里,而不是提交信息里。
|
版本说明按倒序排列。提交信息遵循 [CONTRIBUTING.md](CONTRIBUTING.md) 的单行规范,因此每次改动"为什么这样改、影响面是什么"记在这里,而不是提交信息里。
|
||||||
|
|
||||||
|
## v1.0.9
|
||||||
|
|
||||||
|
- 轻弹窗改成 **DSH 的浮层卡片**,跟随 DSH 的深色 / 浅色模式。原来用的是 DSH 的 `--dsw-alias-toast-bg`,而那个令牌在**两种模式下都是深灰**(浅色模式 `#353638`、深色模式 `#43454a`),所以在浅色页面里浮窗是一块接近纯黑的板子,和周围的卡片不是一种东西。现在照 DSH 自己的做法来(下面这些都是 shell 主题里真实存在、并且会随 `body[data-ds-dark-theme]` 一起切换的令牌):
|
||||||
|
- 底色 `--dsw-alias-bg-layer-2`:浅色 `#fff`、深色 `#2c2c2e`,和浮动菜单 / 弹窗用的是同一层;
|
||||||
|
- 发丝线与大阴影用 `--dsw-elevation-stroke-color` + `--dsw-elevation-prominent`(DSH 菜单的做法,发丝线取 `--dsw-alias-border-l1`:浅色 `#0000000a`、深色 `#ffffff0f`);
|
||||||
|
- 文字改 `--dsw-alias-label-primary` / `-secondary` / `-tertiary`,不再是写死的白字加 `opacity`;
|
||||||
|
- 入场动画用 shell 自己的 `--ds-transition-duration` 与 `--ds-ease-in-out`。
|
||||||
|
- 「查看」按钮改成 `--dsw-alias-state-business-primary`(浅色 `#4176e6`、深色 `#7aaaff`);原来是写死的 `--dsw-static-deepseek-400`,在白底上对比度不够、也不随主题变化。关闭按钮用 `--dsw-alias-label-tertiary`,hover 升到主文字色。三类图标仍是状态令牌:完成绿 `#22c55e`、授权琥珀 `#dd8629`、提问蓝(随主题)。
|
||||||
|
- 新增 `tests/unit/styles.test.js`(5 条)把两条不变量钉住:**样式表里不出现字面颜色、也不用 `--dsw-static-*` 这类不随主题切换的令牌**;弹窗必须用上面那套浮层配方,且**不许再出现 `--dsw-alias-toast-bg`**。以后谁把颜色写死、或又退回那个深色板子,测试会直接失败。
|
||||||
|
- 行为未改:位置(右上角)、尺寸、停留时间、hover 暂停、`Esc` / ✕ 关闭、同屏 3 条都不变,只换皮。
|
||||||
|
|
||||||
## v1.0.8
|
## v1.0.8
|
||||||
|
|
||||||
- 修复:**授权与提问两类提醒从来不会弹**(只有「会话已完成」会弹)。原因不是判据写错了,而是投递前的「是否还值得提醒」那道闸门用错了事实:
|
- 修复:**授权与提问两类提醒从来不会弹**(只有「会话已完成」会弹)。原因不是判据写错了,而是投递前的「是否还值得提醒」那道闸门用错了事实:
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ DeepSeek Harness(DSH)插件:会话**需要你注意**时提醒你——窗
|
|||||||
- 点「查看」跳到该会话;
|
- 点「查看」跳到该会话;
|
||||||
- 6 秒后自动消失;鼠标停在上面期间不消失,移开后重新计时;
|
- 6 秒后自动消失;鼠标停在上面期间不消失,移开后重新计时;
|
||||||
- 按 `Esc` 关掉最新的一条,或点 ✕ 关掉这一条;
|
- 按 `Esc` 关掉最新的一条,或点 ✕ 关掉这一条;
|
||||||
- 同屏最多叠 3 条,更早的会被挤掉。
|
- 同屏最多叠 3 条,更早的会被挤掉;
|
||||||
|
- 轻弹窗是 DSH 的浮层卡片样式(和菜单、弹窗同一层底色与阴影),**跟随 DSH 的深色 / 浅色模式**。
|
||||||
|
|
||||||
## 设置
|
## 设置
|
||||||
|
|
||||||
@@ -59,10 +60,10 @@ DeepSeek Harness(DSH)插件:会话**需要你注意**时提醒你——窗
|
|||||||
在 DSH 的 **插件 → 添加插件** 的上方输入框里填:
|
在 DSH 的 **插件 → 添加插件** 的上方输入框里填:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
https://gitea.iwake.top/dsh-plugin/session-notify.git#v1.0.8
|
https://gitea.iwake.top/dsh-plugin/session-notify.git#v1.0.9
|
||||||
```
|
```
|
||||||
|
|
||||||
`#` 后面跟标签或提交,用来锁定版本;不写则取默认分支。跟随 1.x 最新版可以写 `#semver:^1.0.8`。仓库是公开的,不需要凭据,也不用改「安装源」。
|
`#` 后面跟标签或提交,用来锁定版本;不写则取默认分支。跟随 1.x 最新版可以写 `#semver:^1.0.9`。仓库是公开的,不需要凭据,也不用改「安装源」。
|
||||||
|
|
||||||
### 本地路径安装
|
### 本地路径安装
|
||||||
|
|
||||||
|
|||||||
@@ -891,21 +891,21 @@ function seatTranslator(props, fallback) {
|
|||||||
var CSS = `
|
var CSS = `
|
||||||
.dsn-stack{position:fixed;top:40px;right:24px;z-index:1100;display:flex;flex-direction:column;align-items:flex-end;gap:8px;pointer-events:none}
|
.dsn-stack{position:fixed;top:40px;right:24px;z-index:1100;display:flex;flex-direction:column;align-items:flex-end;gap:8px;pointer-events:none}
|
||||||
@media (max-width:520px){.dsn-stack{right:12px}}
|
@media (max-width:520px){.dsn-stack{right:12px}}
|
||||||
.dsn-toast{box-sizing:border-box;pointer-events:auto;display:flex;align-items:flex-start;gap:10px;width:max-content;max-width:min(420px,calc(100vw - 48px));padding:12px 12px 12px 16px;border-radius:var(--dsw-radius-lg);background:var(--dsw-alias-toast-bg);color:var(--dsw-alias-toast-label);font-size:14px;line-height:22px;box-shadow:var(--dsw-shadow-lv3);animation:dsn-toast-in 160ms ease-out}
|
.dsn-toast{box-sizing:border-box;pointer-events:auto;display:flex;align-items:flex-start;gap:10px;width:max-content;max-width:min(420px,calc(100vw - 48px));padding:12px 12px 12px 16px;border-radius:var(--dsw-radius-lg);background:var(--dsw-alias-bg-layer-2);--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-primary);font-size:14px;line-height:22px;animation:dsn-toast-in var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-out)}
|
||||||
@keyframes dsn-toast-in{from{opacity:0;transform:translateX(8px)}to{opacity:1;transform:translateX(0)}}
|
@keyframes dsn-toast-in{from{opacity:0;transform:translateX(8px)}to{opacity:1;transform:translateX(0)}}
|
||||||
@media (prefers-reduced-motion: reduce){.dsn-toast{animation:none}}
|
@media (prefers-reduced-motion: reduce){.dsn-toast{animation:none}}
|
||||||
.dsn-toast-icon{display:grid;place-items:center;flex:none;height:22px;color:var(--dsw-alias-state-warn-label)}
|
.dsn-toast-icon{display:grid;place-items:center;flex:none;height:22px;color:var(--dsw-alias-state-warn-label)}
|
||||||
.dsn-toast-icon.is-completion{color:var(--dsw-alias-state-success-primary)}
|
.dsn-toast-icon.is-completion{color:var(--dsw-alias-state-success-primary)}
|
||||||
.dsn-toast-icon.is-question{color:var(--dsw-alias-brand-primary)}
|
.dsn-toast-icon.is-question{color:var(--dsw-alias-state-business-primary)}
|
||||||
.dsn-toast-text{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
|
.dsn-toast-text{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
|
||||||
.dsn-toast-title{font-weight:500;font-size:14px;line-height:22px}
|
.dsn-toast-title{color:var(--dsw-alias-label-primary);font-weight:500;font-size:14px;line-height:22px}
|
||||||
.dsn-toast-desc{color:var(--dsw-alias-toast-label);opacity:.8;font-size:13px;line-height:20px;overflow-wrap:break-word}
|
.dsn-toast-desc{color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px;overflow-wrap:break-word}
|
||||||
.dsn-toast-actions{display:flex;align-items:center;gap:2px;flex:none}
|
.dsn-toast-actions{display:flex;align-items:center;gap:2px;flex:none}
|
||||||
.dsn-toast-action{display:inline-flex;align-items:center;height:28px;padding:0 8px;border:0;border-radius:var(--dsw-radius-sm);background:none;color:var(--dsw-static-deepseek-400);font:inherit;font-size:13px;line-height:20px;cursor:pointer}
|
.dsn-toast-action{display:inline-flex;align-items:center;height:28px;padding:0 8px;border:0;border-radius:var(--dsw-radius-sm);background:none;color:var(--dsw-alias-state-business-primary);font:inherit;font-size:13px;line-height:20px;cursor:pointer}
|
||||||
.dsn-toast-action:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
.dsn-toast-action:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
||||||
.dsn-toast-action:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:2px}
|
.dsn-toast-action:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:2px}
|
||||||
.dsn-toast-close{flex:none;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:0;border-radius:var(--dsw-radius-sm);background:transparent;color:var(--dsw-alias-toast-label);opacity:.8;cursor:pointer}
|
.dsn-toast-close{flex:none;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:0;border-radius:var(--dsw-radius-sm);background:transparent;color:var(--dsw-alias-label-tertiary);cursor:pointer}
|
||||||
.dsn-toast-close:hover{background:var(--dsw-alias-interactive-bg-hover);opacity:1}
|
.dsn-toast-close:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
||||||
.dsn-toast-close:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:2px}
|
.dsn-toast-close:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:2px}
|
||||||
.dsn-page{display:flex;flex-direction:column;gap:28px;max-width:560px}
|
.dsn-page{display:flex;flex-direction:column;gap:28px;max-width:560px}
|
||||||
.dsn-lede{margin:0;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}
|
.dsn-lede{margin:0;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@dsh-plugin/session-notify",
|
"name": "@dsh-plugin/session-notify",
|
||||||
"version": "1.0.8",
|
"version": "1.0.9",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "会话完成、需要授权、需要回答时提醒你:窗口不在前台用系统通知,窗口在前台用应用内轻弹窗。",
|
"description": "会话完成、需要授权、需要回答时提醒你:窗口不在前台用系统通知,窗口在前台用应用内轻弹窗。",
|
||||||
|
|||||||
+27
-18
@@ -1,20 +1,29 @@
|
|||||||
/**
|
/**
|
||||||
* Every style declaration this package owns.
|
* Every style declaration this package owns.
|
||||||
*
|
*
|
||||||
* The popup copies the shipped toast surface (a fixed entry in the frame-wide
|
* Both surfaces are built out of the theme's own vocabulary and nothing else, so
|
||||||
* overlay, the `--dsw-alias-toast-bg` / `--dsw-alias-toast-label` pair,
|
* they follow the shell's light and dark modes without a branch of their own (the
|
||||||
* `--dsw-radius-lg`, `--dsw-shadow-lv3`) and adds a title line above the text.
|
* theme swaps every `--dsw-alias-*` under `body[data-ds-dark-theme]`):
|
||||||
*
|
*
|
||||||
* The settings page is a reading order, not a form dump: a lede that says how
|
* - The popup is a floating card, using the same recipe the shipped menus and
|
||||||
* alerts travel, then one group per concern. Inside a group a title introduces a
|
* modals use — `--dsw-alias-bg-layer-2` as the surface, the
|
||||||
* list whose rows put the subject on the left and the single control on the
|
* `--dsw-elevation-stroke-color` / `--dsw-elevation-prominent` pair for its
|
||||||
* right, with hairlines only between rows; the status group is a two-column fact
|
* hairline and shadow, `--dsw-alias-label-*` for text, and the shell's motion
|
||||||
* list, because four sentences stacked in one cell are not scannable. Class
|
* tokens. It deliberately does NOT use `--dsw-alias-toast-bg`, which is a fixed
|
||||||
* names are renamed under `dsn-`; only the popup glyphs carry their own artwork
|
* dark slate (#353638 in light mode) and would read as a black slab on a light
|
||||||
* colors.
|
* page.
|
||||||
|
* - The settings page is a reading order, not a form dump: a lede that says how
|
||||||
|
* alerts travel, then one group per concern. Inside a group a title introduces
|
||||||
|
* a list whose rows put the subject on the left and the single control on the
|
||||||
|
* right, with hairlines only between rows; the status group is a two-column
|
||||||
|
* fact list, because four sentences stacked in one cell are not scannable.
|
||||||
|
*
|
||||||
|
* Class names are renamed under `dsn-`; only the popup glyphs carry their own
|
||||||
|
* artwork colors — and those are theme-aware state tokens too.
|
||||||
*
|
*
|
||||||
* A plugin must not import a Harness Client package, so the declarations are
|
* A plugin must not import a Harness Client package, so the declarations are
|
||||||
* this package's own text and reference theme tokens only.
|
* this package's own text. `tests/unit/styles.test.js` holds both invariants:
|
||||||
|
* tokens only, and no theme-fixed color.
|
||||||
*
|
*
|
||||||
* @module @dsh-plugin/session-notify/client/ui/styles
|
* @module @dsh-plugin/session-notify/client/ui/styles
|
||||||
*/
|
*/
|
||||||
@@ -22,21 +31,21 @@
|
|||||||
export const CSS = `
|
export const CSS = `
|
||||||
.dsn-stack{position:fixed;top:40px;right:24px;z-index:1100;display:flex;flex-direction:column;align-items:flex-end;gap:8px;pointer-events:none}
|
.dsn-stack{position:fixed;top:40px;right:24px;z-index:1100;display:flex;flex-direction:column;align-items:flex-end;gap:8px;pointer-events:none}
|
||||||
@media (max-width:520px){.dsn-stack{right:12px}}
|
@media (max-width:520px){.dsn-stack{right:12px}}
|
||||||
.dsn-toast{box-sizing:border-box;pointer-events:auto;display:flex;align-items:flex-start;gap:10px;width:max-content;max-width:min(420px,calc(100vw - 48px));padding:12px 12px 12px 16px;border-radius:var(--dsw-radius-lg);background:var(--dsw-alias-toast-bg);color:var(--dsw-alias-toast-label);font-size:14px;line-height:22px;box-shadow:var(--dsw-shadow-lv3);animation:dsn-toast-in 160ms ease-out}
|
.dsn-toast{box-sizing:border-box;pointer-events:auto;display:flex;align-items:flex-start;gap:10px;width:max-content;max-width:min(420px,calc(100vw - 48px));padding:12px 12px 12px 16px;border-radius:var(--dsw-radius-lg);background:var(--dsw-alias-bg-layer-2);--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-primary);font-size:14px;line-height:22px;animation:dsn-toast-in var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-out)}
|
||||||
@keyframes dsn-toast-in{from{opacity:0;transform:translateX(8px)}to{opacity:1;transform:translateX(0)}}
|
@keyframes dsn-toast-in{from{opacity:0;transform:translateX(8px)}to{opacity:1;transform:translateX(0)}}
|
||||||
@media (prefers-reduced-motion: reduce){.dsn-toast{animation:none}}
|
@media (prefers-reduced-motion: reduce){.dsn-toast{animation:none}}
|
||||||
.dsn-toast-icon{display:grid;place-items:center;flex:none;height:22px;color:var(--dsw-alias-state-warn-label)}
|
.dsn-toast-icon{display:grid;place-items:center;flex:none;height:22px;color:var(--dsw-alias-state-warn-label)}
|
||||||
.dsn-toast-icon.is-completion{color:var(--dsw-alias-state-success-primary)}
|
.dsn-toast-icon.is-completion{color:var(--dsw-alias-state-success-primary)}
|
||||||
.dsn-toast-icon.is-question{color:var(--dsw-alias-brand-primary)}
|
.dsn-toast-icon.is-question{color:var(--dsw-alias-state-business-primary)}
|
||||||
.dsn-toast-text{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
|
.dsn-toast-text{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
|
||||||
.dsn-toast-title{font-weight:500;font-size:14px;line-height:22px}
|
.dsn-toast-title{color:var(--dsw-alias-label-primary);font-weight:500;font-size:14px;line-height:22px}
|
||||||
.dsn-toast-desc{color:var(--dsw-alias-toast-label);opacity:.8;font-size:13px;line-height:20px;overflow-wrap:break-word}
|
.dsn-toast-desc{color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px;overflow-wrap:break-word}
|
||||||
.dsn-toast-actions{display:flex;align-items:center;gap:2px;flex:none}
|
.dsn-toast-actions{display:flex;align-items:center;gap:2px;flex:none}
|
||||||
.dsn-toast-action{display:inline-flex;align-items:center;height:28px;padding:0 8px;border:0;border-radius:var(--dsw-radius-sm);background:none;color:var(--dsw-static-deepseek-400);font:inherit;font-size:13px;line-height:20px;cursor:pointer}
|
.dsn-toast-action{display:inline-flex;align-items:center;height:28px;padding:0 8px;border:0;border-radius:var(--dsw-radius-sm);background:none;color:var(--dsw-alias-state-business-primary);font:inherit;font-size:13px;line-height:20px;cursor:pointer}
|
||||||
.dsn-toast-action:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
.dsn-toast-action:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
||||||
.dsn-toast-action:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:2px}
|
.dsn-toast-action:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:2px}
|
||||||
.dsn-toast-close{flex:none;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:0;border-radius:var(--dsw-radius-sm);background:transparent;color:var(--dsw-alias-toast-label);opacity:.8;cursor:pointer}
|
.dsn-toast-close{flex:none;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:0;border-radius:var(--dsw-radius-sm);background:transparent;color:var(--dsw-alias-label-tertiary);cursor:pointer}
|
||||||
.dsn-toast-close:hover{background:var(--dsw-alias-interactive-bg-hover);opacity:1}
|
.dsn-toast-close:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
||||||
.dsn-toast-close:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:2px}
|
.dsn-toast-close:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:2px}
|
||||||
.dsn-page{display:flex;flex-direction:column;gap:28px;max-width:560px}
|
.dsn-page{display:flex;flex-direction:column;gap:28px;max-width:560px}
|
||||||
.dsn-lede{margin:0;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}
|
.dsn-lede{margin:0;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
// The stylesheet's two contracts, both of which the popup broke once:
|
||||||
|
//
|
||||||
|
// 1. it may only paint with theme tokens — a literal color cannot follow the
|
||||||
|
// shell's light/dark switch, and a `--dsw-static-*` token is fixed by design;
|
||||||
|
// 2. the popup must use the same floating-surface recipe as the shipped menus and
|
||||||
|
// modals (surface + hairline + elevation + label tokens), not the fixed dark
|
||||||
|
// `--dsw-alias-toast-bg` that made it a black slab on a light page.
|
||||||
|
import { test } from 'node:test'
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
|
||||||
|
import { CSS } from '../../src/client/ui/styles.js'
|
||||||
|
|
||||||
|
/** Every `property:value` declaration in the sheet, selector-less. */
|
||||||
|
function declarations(sheet) {
|
||||||
|
return [...sheet.matchAll(/([a-z-]+)\s*:\s*([^;{}]+);/g)].map((match) => ({ property: match[1], value: match[2].trim() }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const COLOR_LITERAL = /#[0-9a-fA-F]{3,8}\b|\b(?:rgb|rgba|hsl|hsla|oklch|lab)\(|\b(?:white|black|red|green|blue|gray|grey)\b/
|
||||||
|
|
||||||
|
test('the stylesheet paints with tokens and never with a literal color', () => {
|
||||||
|
const properties = ['color', 'background', 'background-color', 'border', 'border-top', 'border-color', 'box-shadow', 'outline']
|
||||||
|
const painted = declarations(CSS).filter(({ property }) => properties.includes(property))
|
||||||
|
assert.ok(painted.length > 20, 'expected the sheet to carry its painted declarations')
|
||||||
|
|
||||||
|
for (const { property, value } of painted) {
|
||||||
|
// Keyword values carry no color of their own.
|
||||||
|
if (/^(none|transparent|inherit|currentColor)$/.test(value)) continue
|
||||||
|
assert.ok(!COLOR_LITERAL.test(value), `${property}: ${value} hardcodes a color instead of using a theme token`)
|
||||||
|
assert.ok(!value.includes('--dsw-static-'), `${property}: ${value} uses a theme-fixed static token that cannot follow dark mode`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('the popup is the shipped floating-card recipe, in both themes', () => {
|
||||||
|
const toast = CSS.match(/\.dsn-toast\{([^}]*)\}/)?.[1]
|
||||||
|
assert.ok(toast !== undefined, 'the popup rule is missing')
|
||||||
|
|
||||||
|
// The surface, the hairline and the shadow are what DSH's menus and modals use.
|
||||||
|
assert.ok(toast.includes('background:var(--dsw-alias-bg-layer-2)'), 'the popup must sit on the elevated layer surface')
|
||||||
|
assert.ok(toast.includes('--dsw-elevation-stroke-color:var(--dsw-alias-border-l1)'), 'the popup must take the menu hairline')
|
||||||
|
assert.ok(toast.includes('box-shadow:var(--dsw-elevation-prominent)'), 'the popup must take the prominent elevation')
|
||||||
|
assert.ok(toast.includes('color:var(--dsw-alias-label-primary)'), 'the popup text must be the primary label token')
|
||||||
|
assert.ok(toast.includes('border-radius:var(--dsw-radius-lg)'), 'the popup must take the large radius')
|
||||||
|
assert.ok(toast.includes('var(--ds-transition-duration') && toast.includes('var(--ds-ease-in-out'), 'the entrance must use the shell motion tokens')
|
||||||
|
|
||||||
|
// The one thing it must not do: the fixed dark toast slab, or any of its labels.
|
||||||
|
assert.ok(!toast.includes('--dsw-alias-toast-'), 'the popup must not use the theme-fixed toast surface')
|
||||||
|
assert.ok(!CSS.includes('--dsw-alias-toast-'), 'no rule may fall back to the fixed toast surface')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('the popup reads as one card: title, body and both controls use label tokens', () => {
|
||||||
|
const rule = (name) => CSS.match(new RegExp(`\\.${name}\\{([^}]*)\\}`))?.[1] ?? ''
|
||||||
|
assert.ok(rule('dsn-toast-title').includes('var(--dsw-alias-label-primary)'))
|
||||||
|
assert.ok(rule('dsn-toast-desc').includes('var(--dsw-alias-label-secondary)'))
|
||||||
|
assert.ok(rule('dsn-toast-close').includes('color:var(--dsw-alias-label-tertiary)'))
|
||||||
|
assert.ok(rule('dsn-toast-action').includes('var(--dsw-alias-state-business-primary)'))
|
||||||
|
// Hover feedback comes from the shared interactive token, not a hand-rolled color.
|
||||||
|
assert.ok(rule('dsn-toast-action:hover').includes('var(--dsw-alias-interactive-bg-hover)'))
|
||||||
|
assert.ok(rule('dsn-toast-close:hover').includes('var(--dsw-alias-interactive-bg-hover)'))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('the three popup glyphs stay distinguishable through state tokens', () => {
|
||||||
|
assert.ok(CSS.includes('.dsn-toast-icon{') && CSS.includes('var(--dsw-alias-state-warn-label)'))
|
||||||
|
assert.ok(CSS.includes('.dsn-toast-icon.is-completion{color:var(--dsw-alias-state-success-primary)}'))
|
||||||
|
assert.ok(CSS.includes('.dsn-toast-icon.is-question{color:var(--dsw-alias-state-business-primary)}'))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('every class the components render has a rule, so nothing falls back to defaults', () => {
|
||||||
|
// Guards the redesign: a renamed class that the sheet no longer styles would
|
||||||
|
// render as an unstyled block instead of failing loudly.
|
||||||
|
const expected = [
|
||||||
|
'dsn-stack', 'dsn-toast', 'dsn-toast-icon', 'dsn-toast-icon is-completion', 'dsn-toast-icon is-question',
|
||||||
|
'dsn-toast-text', 'dsn-toast-title', 'dsn-toast-desc', 'dsn-toast-actions', 'dsn-toast-action', 'dsn-toast-close',
|
||||||
|
'dsn-page', 'dsn-lede', 'dsn-group', 'dsn-group-title', 'dsn-group-note', 'dsn-list', 'dsn-row', 'dsn-row-text',
|
||||||
|
'dsn-row-title', 'dsn-row-sub', 'dsn-state', 'dsn-dot', 'dsn-facts', 'dsn-fact-key', 'dsn-fact', 'dsn-fact-value',
|
||||||
|
'dsn-fact-title', 'dsn-switch', 'dsn-thumb', 'dsn-button',
|
||||||
|
]
|
||||||
|
for (const className of expected) {
|
||||||
|
const selector = `.${className.split(' ').join('.')}`
|
||||||
|
assert.ok(CSS.includes(`${selector}{`) || CSS.includes(`${selector}[`) || CSS.includes(`${selector}:`) || CSS.includes(`${selector}.`), `no rule for ${selector}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user