Files
fusion/packages/dashboard/app/__tests__/spinner-animation.css.test.ts
gsxdsm b6c15fe470 FN-5855: fix frozen dashboard spinners
Restore shared spinner animation behavior for dashboard AI activity indicators.

- switch BackgroundTasksIndicator loaders to the shared animate-spin utility class
- anchor shared spin utilities on SVG centers with transform-origin and transform-box rules
- add CSS regression tests covering global spin keyframes and spinner utility styles

Files changed:
 .../app/__tests__/spinner-animation.css.test.ts    | 50 ++++++++++++++++++++++
 .../app/components/BackgroundTasksIndicator.tsx    |  9 ++--
 packages/dashboard/app/styles.css                  |  7 +++
 3 files changed, 60 insertions(+), 6 deletions(-)

Fusion-Task-Id: FN-5855

Fusion-Task-Lineage: b94d4333-2e78-4c4b-a304-5a13b797bbc6
2026-06-01 23:29:21 -07:00

51 lines
1.9 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { readFileSync } from "fs";
import { resolve } from "path";
function extractBlock(content: string, pattern: RegExp): string {
const match = content.match(pattern);
expect(match?.index).toBeDefined();
const start = match!.index! + match![0].length;
let index = start;
let depth = 1;
while (index < content.length && depth > 0) {
if (content[index] === "{") depth += 1;
if (content[index] === "}") depth -= 1;
index += 1;
}
expect(depth).toBe(0);
return content.slice(match!.index!, index);
}
describe("global spinner animation utility", () => {
const css = readFileSync(resolve(__dirname, "../styles.css"), "utf8");
it("keeps top-level spin keyframes rotating to 360deg", () => {
const topLevelSpinBlock = extractBlock(css, /@keyframes\s+spin\s*\{/);
expect(topLevelSpinBlock).toContain("transform: rotate(360deg);");
expect(css.indexOf("@keyframes spin")).toBeLessThan(css.indexOf(":root {\n --bg:"));
});
it("keeps the shared animate-spin and spin utilities running infinitely", () => {
const animateSpinBlock = css.match(/\.animate-spin\s*\{[\s\S]*?\}/)?.[0] ?? "";
const spinBlock = css.match(/\.spin\s*\{[\s\S]*?\}/)?.[0] ?? "";
expect(animateSpinBlock).toContain("animation: spin 1s linear infinite;");
expect(spinBlock).toContain("animation: spin 1s linear infinite;");
});
it("anchors SVG spinners around their own center", () => {
const animateSpinBlock = css.match(/\.animate-spin\s*\{[\s\S]*?\}/)?.[0] ?? "";
const spinBlock = css.match(/\.spin\s*\{[\s\S]*?\}/)?.[0] ?? "";
const svgSpinnerBlock = css.match(/svg\.animate-spin,\s*svg\.spin\s*\{[\s\S]*?\}/)?.[0] ?? "";
expect(animateSpinBlock).toContain("transform-origin: center;");
expect(spinBlock).toContain("transform-origin: center;");
expect(svgSpinnerBlock).toContain("transform-box: fill-box;");
});
});