@celestia-island/hikari 0.55.230 → 0.55.231
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/package.json
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it } from "vitest";
|
|
2
2
|
import { createApp, defineComponent, h, nextTick, ref } from "vue";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
3
6
|
|
|
4
7
|
import { HkAvatarButton } from "./HkAvatarButton";
|
|
5
8
|
import { HkAdminHeader } from "./HkAdminHeader";
|
|
@@ -107,6 +110,61 @@ describe("HkAvatarButton", () => {
|
|
|
107
110
|
});
|
|
108
111
|
});
|
|
109
112
|
|
|
113
|
+
describe("HkAvatarButton sheet contract", () => {
|
|
114
|
+
// happy-dom has no layout engine, so the geometry is pinned at the
|
|
115
|
+
// source level — the same approach the downstream chest tripwire
|
|
116
|
+
// (headerAvatarGeometry.test.ts) takes against the INSTALLED sheet.
|
|
117
|
+
const sheet = readFileSync(
|
|
118
|
+
join(dirname(fileURLToPath(import.meta.url)), "..", "styles", "admin-tokens.scss"),
|
|
119
|
+
"utf8",
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
/** Declarations of the rule whose whole prelude IS `selector`
|
|
123
|
+
* (a compound/descendant spelling is a different rule and does not
|
|
124
|
+
* match), comments stripped; null when no such rule exists — every
|
|
125
|
+
* assertion treats null as a failure, never as "no objection". */
|
|
126
|
+
function ruleDeclarations(selector: string): Array<[string, string]> | null {
|
|
127
|
+
const code = sheet
|
|
128
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
129
|
+
.replace(/(^|[^:])\/\/[^\n]*/gm, "$1");
|
|
130
|
+
for (let at = code.indexOf(selector); at >= 0; at = code.indexOf(selector, at + 1)) {
|
|
131
|
+
const open = code.indexOf("{", at + selector.length);
|
|
132
|
+
if (open < 0) continue;
|
|
133
|
+
if (code.slice(at + selector.length, open).trim() !== "") continue;
|
|
134
|
+
let boundary = at - 1;
|
|
135
|
+
while (boundary >= 0 && !"}{;".includes(code[boundary])) boundary -= 1;
|
|
136
|
+
if (code.slice(boundary + 1, at).replace(/\s+/g, " ").trim() !== "") continue;
|
|
137
|
+
const body = code.slice(open + 1, code.indexOf("}", open));
|
|
138
|
+
const out: Array<[string, string]> = [];
|
|
139
|
+
for (const part of body.split(";")) {
|
|
140
|
+
const m = /^\s*([-a-zA-Z]+)\s*:\s*([\s\S]+?)\s*$/.exec(part);
|
|
141
|
+
if (m) out.push([m[1], m[2].replace(/\s+/g, " ")]);
|
|
142
|
+
}
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
it("the trigger is a BLOCK — hosts may nest it in a plain wrapper div", () => {
|
|
149
|
+
// The chat face wraps HeaderLeft's trigger in a menu-anchor <div>:
|
|
150
|
+
// an inline-block button there joins a line box and the strut's
|
|
151
|
+
// descender space lands BELOW it, riding the avatar ~4px high inside
|
|
152
|
+
// a perfectly centred wrapper (2026-10-30 user report). Removing
|
|
153
|
+
// this declaration is the mutation this test must catch.
|
|
154
|
+
const rule = ruleDeclarations(".s-admin-header-avatar");
|
|
155
|
+
expect(rule, "admin-tokens must declare .s-admin-header-avatar").not.toBeNull();
|
|
156
|
+
// Positive control for the extractor itself: the geometry pair the
|
|
157
|
+
// downstream tripwire also reads. If these stop matching, the parser
|
|
158
|
+
// above is broken — fix it, not the assertion below.
|
|
159
|
+
expect(rule).toContainEqual(["width", "var(--space-28)"]);
|
|
160
|
+
expect(rule).toContainEqual(["height", "var(--space-28)"]);
|
|
161
|
+
expect(rule, "the trigger must not re-enter inline formatting").toContainEqual([
|
|
162
|
+
"display",
|
|
163
|
+
"block",
|
|
164
|
+
]);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
110
168
|
describe("HkAdminHeader × HkAvatarButton / identity slot", () => {
|
|
111
169
|
it("never renders aria-expanded when no expanded prop is given (drawer-mode contract)", () => {
|
|
112
170
|
// R2 S3-2: menu engines read this pair; a drawer-mode trigger has no
|
|
@@ -574,6 +574,16 @@
|
|
|
574
574
|
}
|
|
575
575
|
|
|
576
576
|
.s-admin-header-avatar {
|
|
577
|
+
/* Block, never the UA's inline-block: hosts may nest the trigger in a
|
|
578
|
+
* plain block wrapper (the chat face's menu-anchor <div> around
|
|
579
|
+
* HeaderLeft's HkAvatarButton), and an inline-block button there joins
|
|
580
|
+
* a line box — its baseline (the bottom margin edge, because of
|
|
581
|
+
* `overflow: hidden`) sits on the strut's baseline, so the strut's
|
|
582
|
+
* descender space lands BELOW the avatar and the button rides ~4px
|
|
583
|
+
* high inside a perfectly centred wrapper (2026-10-30 user report:
|
|
584
|
+
* 前台头像略微偏上,后台正中). A direct flex/grid parent blockifies
|
|
585
|
+
* this away, so the console face is unaffected either way. */
|
|
586
|
+
display: block;
|
|
577
587
|
position: relative;
|
|
578
588
|
width: var(--space-28);
|
|
579
589
|
height: var(--space-28);
|