@crvy/rprtr 0.0.9 → 0.1.1

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/dist/index.js CHANGED
@@ -19391,10 +19391,29 @@ var CrvyRprtrSuiteSchema = external_exports.lazy(
19391
19391
  children: external_exports.record(external_exports.string(), external_exports.union([CrvyRprtrSuiteSchema, CrvyRprtrTestSchema])).optional()
19392
19392
  })
19393
19393
  );
19394
- var WebSocketMessageSchema = external_exports.object({
19394
+ var IncomingWebSocketMessageSchema = external_exports.object({
19395
19395
  type: external_exports.enum(["test-begin", "test-end", "run-end", "approve", "sync"]),
19396
19396
  data: external_exports.unknown()
19397
19397
  });
19398
+ var WebSocketMessageSchema = external_exports.discriminatedUnion("type", [
19399
+ external_exports.object({ type: external_exports.literal("test-begin"), data: TestDataSchema }),
19400
+ external_exports.object({ type: external_exports.literal("test-update"), data: TestDataSchema }),
19401
+ external_exports.object({
19402
+ type: external_exports.literal("run-end"),
19403
+ data: external_exports.object({
19404
+ status: external_exports.enum(["passed", "failed", "skipped"]),
19405
+ removedTestIds: external_exports.array(external_exports.string())
19406
+ })
19407
+ }),
19408
+ external_exports.object({
19409
+ type: external_exports.literal("sync"),
19410
+ data: external_exports.object({
19411
+ tests: external_exports.record(external_exports.string(), TestDataSchema),
19412
+ isUpdateMode: external_exports.boolean().optional()
19413
+ })
19414
+ }),
19415
+ external_exports.object({ type: external_exports.literal("approve"), data: external_exports.unknown() })
19416
+ ]);
19398
19417
  var TestBeginDataSchema = external_exports.object({
19399
19418
  id: external_exports.string(),
19400
19419
  title: external_exports.string(),
@@ -19414,6 +19433,9 @@ var TestEndDataSchema = external_exports.object({
19414
19433
  error: external_exports.string().optional(),
19415
19434
  duration: external_exports.number().optional()
19416
19435
  });
19436
+ var RunEndDataSchema = external_exports.object({
19437
+ status: external_exports.enum(["passed", "failed", "skipped"])
19438
+ });
19417
19439
  var ReportDataSchema = external_exports.object({
19418
19440
  isRunning: external_exports.boolean(),
19419
19441
  tests: external_exports.record(external_exports.string(), TestDataSchema),
@@ -19678,19 +19700,269 @@ function treeifyTests(testsById) {
19678
19700
  });
19679
19701
  return rootSuite;
19680
19702
  }
19681
- function mergeTreeState(target, source2) {
19682
- target.opened = source2.opened;
19683
- target.checked = source2.checked;
19684
- target.indeterminate = source2.indeterminate;
19685
- for (const [key2, targetChild] of getChildrenEntries(target.children)) {
19686
- const sourceChild = source2.children?.[key2];
19687
- if (targetChild === void 0 || sourceChild === void 0) continue;
19688
- if (!isTest(targetChild) && !isTest(sourceChild)) {
19689
- mergeTreeState(targetChild, sourceChild);
19690
- } else if (isTest(targetChild) && isTest(sourceChild)) {
19691
- targetChild.checked = sourceChild.checked;
19703
+
19704
+ // src/client/helpers/test-equality.ts
19705
+ function isTestDataEqual(a, b) {
19706
+ if (a === b) return true;
19707
+ if (a.id !== b.id) return false;
19708
+ if (a.title !== b.title) return false;
19709
+ if (a.browser !== b.browser) return false;
19710
+ if (a.skip !== b.skip) return false;
19711
+ if (a.status !== b.status) return false;
19712
+ if (a.retries !== b.retries) return false;
19713
+ if (!arraysShallowEqual(a.titlePath, b.titlePath ?? [])) return false;
19714
+ if (!approvedEqual(a.approved, b.approved)) return false;
19715
+ if (!resultsEqual(a.results, b.results)) return false;
19716
+ if (!attachmentsEqual(a.attachments, b.attachments)) return false;
19717
+ if (!locationsEqual(a.location, b.location)) return false;
19718
+ return true;
19719
+ }
19720
+ function arraysShallowEqual(a, b) {
19721
+ if (a === b) return true;
19722
+ if (a.length !== b.length) return false;
19723
+ for (let i = 0; i < a.length; i++) {
19724
+ if (a[i] !== b[i]) return false;
19725
+ }
19726
+ return true;
19727
+ }
19728
+ function approvedEqual(a, b) {
19729
+ if (a === b) return true;
19730
+ if (a === null || a === void 0 || b === null || b === void 0) return false;
19731
+ const aKeys = Object.keys(a);
19732
+ const bKeys = Object.keys(b);
19733
+ if (aKeys.length !== bKeys.length) return false;
19734
+ for (const key2 of aKeys) {
19735
+ if (a[key2] !== b[key2]) return false;
19736
+ }
19737
+ return true;
19738
+ }
19739
+ function resultsEqual(a, b) {
19740
+ if (a === b) return true;
19741
+ if (a === void 0 || b === void 0) return false;
19742
+ if (a.length !== b.length) return false;
19743
+ for (let i = 0; i < a.length; i++) {
19744
+ const ai = a[i];
19745
+ const bi = b[i];
19746
+ if (ai === bi) continue;
19747
+ if (ai === void 0 || bi === void 0) return false;
19748
+ if (!resultEqual(ai, bi)) return false;
19749
+ }
19750
+ return true;
19751
+ }
19752
+ function resultEqual(a, b) {
19753
+ if (a === b) return true;
19754
+ if (a.status !== b.status) return false;
19755
+ if (a.retries !== b.retries) return false;
19756
+ if (a.error !== b.error) return false;
19757
+ if (a.duration !== b.duration) return false;
19758
+ if (!imagesEqual(a.images, b.images)) return false;
19759
+ if (!visualDeclarationsEqual(a.visualDeclarations, b.visualDeclarations)) return false;
19760
+ return true;
19761
+ }
19762
+ function imagesEqual(a, b) {
19763
+ if (a === b) return true;
19764
+ if (a === void 0 || b === void 0) return false;
19765
+ const aKeys = Object.keys(a);
19766
+ const bKeys = Object.keys(b);
19767
+ if (aKeys.length !== bKeys.length) return false;
19768
+ for (const key2 of aKeys) {
19769
+ const ai = a[key2];
19770
+ const bi = b[key2];
19771
+ if (ai === bi) continue;
19772
+ if (ai === void 0 || bi === void 0) return false;
19773
+ if (ai.actual !== bi.actual) return false;
19774
+ if (ai.expect !== bi.expect) return false;
19775
+ if (ai.diff !== bi.diff) return false;
19776
+ if (ai.error !== bi.error) return false;
19777
+ if (ai.source !== bi.source) return false;
19778
+ }
19779
+ return true;
19780
+ }
19781
+ function visualDeclarationsEqual(a, b) {
19782
+ if (a === b) return true;
19783
+ if (a === void 0 || b === void 0) return false;
19784
+ if (a.length !== b.length) return false;
19785
+ for (let i = 0; i < a.length; i++) {
19786
+ const ai = a[i];
19787
+ const bi = b[i];
19788
+ if (ai === bi) continue;
19789
+ if (ai === void 0 || bi === void 0) return false;
19790
+ if (ai.visualName !== bi.visualName) return false;
19791
+ if (ai.kind !== bi.kind) return false;
19792
+ if (ai.kind === "named") {
19793
+ if (bi.kind !== "named") return false;
19794
+ if (ai.declaredName !== bi.declaredName) return false;
19795
+ if (ai.snapshotBaseName !== bi.snapshotBaseName) return false;
19796
+ }
19797
+ if (ai.occurrenceIndex !== bi.occurrenceIndex) return false;
19798
+ }
19799
+ return true;
19800
+ }
19801
+ function attachmentsEqual(a, b) {
19802
+ if (a === b) return true;
19803
+ if (a === void 0 || b === void 0) return false;
19804
+ if (a.length !== b.length) return false;
19805
+ for (let i = 0; i < a.length; i++) {
19806
+ const ai = a[i];
19807
+ const bi = b[i];
19808
+ if (ai === bi) continue;
19809
+ if (ai === void 0 || bi === void 0) return false;
19810
+ if (ai.name !== bi.name) return false;
19811
+ if (ai.path !== bi.path) return false;
19812
+ if (ai.contentType !== bi.contentType) return false;
19813
+ }
19814
+ return true;
19815
+ }
19816
+ function locationsEqual(a, b) {
19817
+ if (a === b) return true;
19818
+ if (a === void 0 || b === void 0) return false;
19819
+ return a.file === b.file && a.line === b.line;
19820
+ }
19821
+ function copyMutableFields(target, source2) {
19822
+ if (target.status !== source2.status) target.status = source2.status;
19823
+ if (target.skip !== source2.skip) target.skip = source2.skip;
19824
+ if (target.retries !== source2.retries) target.retries = source2.retries;
19825
+ if (!approvedEqual(target.approved, source2.approved)) target.approved = source2.approved;
19826
+ if (!resultsEqual(target.results, source2.results)) target.results = source2.results;
19827
+ if (!attachmentsEqual(target.attachments, source2.attachments)) target.attachments = source2.attachments;
19828
+ if (!locationsEqual(target.location, source2.location)) target.location = source2.location;
19829
+ }
19830
+
19831
+ // src/client/helpers/tree-sync.ts
19832
+ function collectOldTests(suite, parentPath, out) {
19833
+ for (const [key2, child2] of getChildrenEntries(suite.children)) {
19834
+ if (child2 === void 0) continue;
19835
+ if (isTest(child2)) {
19836
+ out.set(child2.id, { test: child2, parent: suite, parentPath, browserKey: key2 });
19837
+ } else {
19838
+ collectOldTests(child2, [...parentPath, key2], out);
19839
+ }
19840
+ }
19841
+ }
19842
+ function collectTestsById(suite) {
19843
+ const out = {};
19844
+ function walk(node) {
19845
+ for (const child2 of getChildrenArray(node.children)) {
19846
+ if (child2 === void 0) continue;
19847
+ if (isTest(child2)) {
19848
+ out[child2.id] = child2;
19849
+ } else {
19850
+ walk(child2);
19851
+ }
19852
+ }
19853
+ }
19854
+ walk(suite);
19855
+ return out;
19856
+ }
19857
+ function pathTokensFor(test) {
19858
+ const titlePath = test.titlePath ?? [];
19859
+ const title = test.title;
19860
+ const browser = test.browser ?? "";
19861
+ if (title === void 0 || title === "" || browser === "") return null;
19862
+ const pathParts = [...titlePath, title, browser].filter((p) => p !== void 0 && p !== "");
19863
+ const reversed = pathParts.reverse();
19864
+ const browserKey = reversed[0];
19865
+ if (browserKey === void 0) return null;
19866
+ return { suitePath: reversed.slice(1).reverse(), browserKey };
19867
+ }
19868
+ function ensureSuitePath(root10, path) {
19869
+ let suite = root10;
19870
+ for (const token of path) {
19871
+ suite.children = suite.children ?? {};
19872
+ const existing = suite.children[token];
19873
+ if (existing !== void 0 && !isTest(existing)) {
19874
+ suite = existing;
19875
+ continue;
19876
+ }
19877
+ const nextSuite = {
19878
+ path: [...suite.path, token],
19879
+ skip: false,
19880
+ opened: false,
19881
+ checked: true,
19882
+ indeterminate: false,
19883
+ children: {}
19884
+ };
19885
+ suite.children[token] = nextSuite;
19886
+ suite = nextSuite;
19887
+ }
19888
+ return suite;
19889
+ }
19890
+ function pruneEmptySuites(suite) {
19891
+ if (suite.children === void 0) return;
19892
+ for (const [key2, child2] of getChildrenEntries(suite.children)) {
19893
+ if (child2 === void 0 || isTest(child2)) continue;
19894
+ pruneEmptySuites(child2);
19895
+ if (getChildrenKeys(child2.children).length === 0) {
19896
+ delete suite.children?.[key2];
19897
+ }
19898
+ }
19899
+ }
19900
+ function updateTestInPlace(target, source2) {
19901
+ copyMutableFields(target, source2);
19902
+ }
19903
+ function recalcAncestorStatuses(root10, parentSuitePath) {
19904
+ for (let i = parentSuitePath.length; i > 0; i--) {
19905
+ const ancestorPath = parentSuitePath.slice(0, i);
19906
+ const ancestor = getSuiteByPath(root10, ancestorPath);
19907
+ if (ancestor === void 0 || isTest(ancestor)) continue;
19908
+ const childStatuses = getChildrenArray(ancestor.children).map(({ status }) => status);
19909
+ ancestor.status = childStatuses.length === 0 ? void 0 : childStatuses.reduce(calcStatus);
19910
+ }
19911
+ const rootChildStatuses = getChildrenArray(root10.children).map(({ status }) => status);
19912
+ root10.status = rootChildStatuses.length === 0 ? void 0 : rootChildStatuses.reduce(calcStatus);
19913
+ }
19914
+ function applyTestsToTree(target, testsById, oldTests, expectedIds, touchedSuitePaths) {
19915
+ let changed = false;
19916
+ for (const newTest of Object.values(testsById)) {
19917
+ if (newTest === void 0) continue;
19918
+ const tokens = pathTokensFor(newTest);
19919
+ if (tokens === null) continue;
19920
+ const oldEntry = oldTests.get(newTest.id);
19921
+ if (oldEntry !== void 0 && oldEntry.browserKey === tokens.browserKey) {
19922
+ if (!isTestDataEqual(oldEntry.test, newTest)) {
19923
+ const statusChanged = oldEntry.test.status !== newTest.status;
19924
+ updateTestInPlace(oldEntry.test, newTest);
19925
+ changed = true;
19926
+ if (statusChanged) {
19927
+ touchedSuitePaths.add(tokens.suitePath.join("\0"));
19928
+ }
19929
+ }
19930
+ } else {
19931
+ if (oldEntry !== void 0) {
19932
+ delete oldEntry.parent.children?.[oldEntry.browserKey];
19933
+ }
19934
+ const parent = ensureSuitePath(target, tokens.suitePath);
19935
+ parent.children = parent.children ?? {};
19936
+ parent.children[tokens.browserKey] = {
19937
+ ...newTest,
19938
+ checked: oldEntry?.test.checked ?? true
19939
+ };
19940
+ changed = true;
19941
+ touchedSuitePaths.add(tokens.suitePath.join("\0"));
19942
+ }
19943
+ }
19944
+ for (const [id, oldEntry] of oldTests) {
19945
+ if (!expectedIds.has(id)) {
19946
+ delete oldEntry.parent.children?.[oldEntry.browserKey];
19947
+ touchedSuitePaths.add(oldEntry.parentPath.join("\0"));
19948
+ changed = true;
19692
19949
  }
19693
19950
  }
19951
+ return changed;
19952
+ }
19953
+ function syncTreeState(target, testsById) {
19954
+ const oldTests = /* @__PURE__ */ new Map();
19955
+ collectOldTests(target, [], oldTests);
19956
+ const expectedIds = new Set(Object.keys(testsById));
19957
+ const touchedSuitePaths = /* @__PURE__ */ new Set();
19958
+ const changed = applyTestsToTree(target, testsById, oldTests, expectedIds, touchedSuitePaths);
19959
+ for (const joinedPath of touchedSuitePaths) {
19960
+ recalcAncestorStatuses(target, joinedPath.split("\0"));
19961
+ }
19962
+ if (changed) {
19963
+ pruneEmptySuites(target);
19964
+ }
19965
+ return changed;
19694
19966
  }
19695
19967
 
19696
19968
  // src/client/helpers/suite.ts
@@ -20356,7 +20628,7 @@ function SideBySideView($$anchor, $$props) {
20356
20628
  });
20357
20629
  }
20358
20630
  reset(div);
20359
- template_effect(() => classes = set_class(div, 1, "flex gap-3 items-start", null, classes, {
20631
+ template_effect(() => classes = set_class(div, 1, "flex gap-3 items-center", null, classes, {
20360
20632
  "flex-col": get2(isLandscape),
20361
20633
  "flex-row": !get2(isLandscape)
20362
20634
  }));
@@ -20445,7 +20717,7 @@ delegate(["click", "keydown"]);
20445
20717
  var root_16 = from_html(`<div class="absolute w-full h-full flex"><img alt="Expected" class="max-w-full border border-green-500 invert"/></div>`);
20446
20718
  var root_26 = from_html(`<img alt="Diff" class="border border-transparent max-w-full invert opacity-0"/>`);
20447
20719
  var root_33 = from_html(`<div class="absolute w-full h-full flex"><img alt="Actual" class="max-w-full border border-red-500 mix-blend-difference invert"/></div>`);
20448
- var root5 = from_html(`<div class="flex flex-col gap-3"><div class="flex flex-col bg-surface-panel rounded-md overflow-hidden w-fit max-w-full border-2 border-purple-500/60"><h3 class="m-0 px-3 py-2 text-xs font-bold bg-purple-500/25 text-purple-800 dark:text-purple-400 uppercase tracking-wider">Blend (Difference)</h3> <div class="p-2"><div class="relative flex invert"><!> <!> <!></div></div></div></div>`);
20720
+ var root5 = from_html(`<div class="flex flex-col gap-3 items-center"><div class="flex flex-col bg-surface-panel rounded-md overflow-hidden w-fit max-w-full border-2 border-purple-500/60"><h3 class="m-0 px-3 py-2 text-xs font-bold bg-purple-500/25 text-purple-800 dark:text-purple-400 uppercase tracking-wider">Blend (Difference)</h3> <div class="p-2"><div class="relative flex invert"><!> <!> <!></div></div></div></div>`);
20449
20721
  function BlendView($$anchor, $$props) {
20450
20722
  push($$props, true);
20451
20723
  var div = root5();
@@ -20802,21 +21074,21 @@ var root_29 = from_html(`<div class="flex-1 flex items-center justify-center tex
20802
21074
  var root8 = from_html(`<div class="flex h-dvh relative max-md:flex-col overflow-hidden"><!> <div class="flex-1 flex flex-col overflow-hidden min-w-0"><!></div> <div class="absolute top-3 right-3 max-md:top-1 max-md:right-1 z-10"><!></div></div>`);
20803
21075
  function App($$anchor, $$props) {
20804
21076
  push($$props, true);
20805
- let tests = state(proxy($$props.initialTests));
21077
+ let tests = proxy($$props.initialTests);
20806
21078
  let isRunning = false;
20807
21079
  let openedTestPath = state(proxy([]));
20808
21080
  let filter = state(proxy({ status: null, subStrings: [] }));
20809
21081
  let viewMode = state(proxy(getViewMode()));
20810
21082
  let focusedPath = state(proxy([]));
20811
21083
  let isDark = state(localStorage.getItem("crvy-rprtr-theme") !== "light");
20812
- let openedTest = user_derived(() => getTestByPath(get2(tests), get2(openedTestPath)));
20813
- let failedTests = user_derived(() => getFailedTests(get2(tests)).filter(hasScreenshots));
21084
+ let openedTest = user_derived(() => getTestByPath(tests, get2(openedTestPath)));
21085
+ let failedTests = user_derived(() => getFailedTests(tests).filter(hasScreenshots));
20814
21086
  let retry = state(0);
20815
21087
  let imageName = state("");
20816
21088
  let testResult = user_derived(() => get2(openedTest)?.results?.[get2(retry) - 1] ?? null);
20817
21089
  let currentImage = user_derived(() => get2(testResult)?.images?.[get2(imageName)] ?? null);
20818
21090
  let canApprove = user_derived(() => $$props.approvalEnabled && Boolean(get2(openedTest)?.results?.[get2(retry) - 1]?.images && get2(openedTest).approved?.[get2(imageName)] !== get2(retry) - 1 && get2(openedTest).results[get2(retry) - 1]?.status !== "success"));
20819
- let suiteList = user_derived(() => flattenSuite(filterTests(get2(tests), get2(filter))));
21091
+ let suiteList = user_derived(() => flattenSuite(filterTests(tests, get2(filter))));
20820
21092
  user_effect(() => {
20821
21093
  if (get2(openedTest)) {
20822
21094
  const r2 = get2(openedTest).results?.length ?? 0;
@@ -20839,14 +21111,14 @@ function App($$anchor, $$props) {
20839
21111
  const handlePopState = (event2) => {
20840
21112
  const state2 = event2.state;
20841
21113
  if (state2?.testPath && Array.isArray(state2.testPath)) {
20842
- openSuite(get2(tests), state2.testPath, true);
21114
+ openSuite(tests, state2.testPath, true);
20843
21115
  set(openedTestPath, state2.testPath, true);
20844
21116
  }
20845
21117
  };
20846
21118
  window.addEventListener("popstate", handlePopState);
20847
21119
  const testPath = getTestPathFromSearch();
20848
21120
  if (testPath.length > 0) {
20849
- openSuite(get2(tests), testPath, true);
21121
+ openSuite(tests, testPath, true);
20850
21122
  set(openedTestPath, testPath, true);
20851
21123
  }
20852
21124
  return () => window.removeEventListener("popstate", handlePopState);
@@ -20880,18 +21152,18 @@ function App($$anchor, $$props) {
20880
21152
  }
20881
21153
  case "ArrowRight": {
20882
21154
  if (get2(focusedPath).length === 0) return;
20883
- const focused = getSuiteByPath(get2(tests), get2(focusedPath));
21155
+ const focused = getSuiteByPath(tests, get2(focusedPath));
20884
21156
  if (focused && !isTest(focused)) {
20885
- openSuite(get2(tests), focused.path, true);
21157
+ openSuite(tests, focused.path, true);
20886
21158
  }
20887
21159
  break;
20888
21160
  }
20889
21161
  case "ArrowLeft": {
20890
21162
  if (get2(focusedPath).length === 0) return;
20891
- const focused = getSuiteByPath(get2(tests), get2(focusedPath));
21163
+ const focused = getSuiteByPath(tests, get2(focusedPath));
20892
21164
  if (!focused) return;
20893
21165
  if (!isTest(focused) && focused.opened) {
20894
- openSuite(get2(tests), focused.path, false);
21166
+ openSuite(tests, focused.path, false);
20895
21167
  } else {
20896
21168
  const parentPath = isTest(focused) ? getTestPath(focused) : focused.path;
20897
21169
  set(focusedPath, parentPath.slice(0, -1), true);
@@ -20900,12 +21172,12 @@ function App($$anchor, $$props) {
20900
21172
  }
20901
21173
  case "Enter": {
20902
21174
  if (get2(focusedPath).length === 0) return;
20903
- const focused = getSuiteByPath(get2(tests), get2(focusedPath));
21175
+ const focused = getSuiteByPath(tests, get2(focusedPath));
20904
21176
  if (!focused) return;
20905
21177
  if (isTest(focused) && focused.results?.length) {
20906
21178
  handleOpenTest(focused);
20907
21179
  } else if (!isTest(focused)) {
20908
- openSuite(get2(tests), focused.path, !focused.opened);
21180
+ openSuite(tests, focused.path, !focused.opened);
20909
21181
  }
20910
21182
  break;
20911
21183
  }
@@ -20913,10 +21185,10 @@ function App($$anchor, $$props) {
20913
21185
  if (e.altKey) return;
20914
21186
  if (get2(focusedPath).length === 0) return;
20915
21187
  e.preventDefault();
20916
- const focused = getSuiteByPath(get2(tests), get2(focusedPath));
21188
+ const focused = getSuiteByPath(tests, get2(focusedPath));
20917
21189
  if (!focused) return;
20918
21190
  const path = isTest(focused) ? getTestPath(focused) : focused.path;
20919
- checkSuite(get2(tests), path, !focused.checked);
21191
+ checkSuite(tests, path, !focused.checked);
20920
21192
  break;
20921
21193
  }
20922
21194
  }
@@ -20934,14 +21206,14 @@ function App($$anchor, $$props) {
20934
21206
  const testPath = getTestPath(test);
20935
21207
  setSearchParams(testPath);
20936
21208
  set(focusedPath, testPath, true);
20937
- openSuite(get2(tests), testPath, true);
21209
+ openSuite(tests, testPath, true);
20938
21210
  set(openedTestPath, testPath, true);
20939
21211
  }
20940
21212
  function handleSuiteOpen(path, opened) {
20941
- openSuite(get2(tests), path, opened);
21213
+ openSuite(tests, path, opened);
20942
21214
  }
20943
21215
  function handleSuiteToggle(path, checked) {
20944
- checkSuite(get2(tests), path, checked);
21216
+ checkSuite(tests, path, checked);
20945
21217
  }
20946
21218
  function handleGoToNextFailed() {
20947
21219
  if (get2(failedTests).length === 0) return;
@@ -20965,7 +21237,7 @@ function App($$anchor, $$props) {
20965
21237
  const allApproved = Object.keys(result.images).every((name) => get2(openedTest).approved?.[name] === get2(retry) - 1);
20966
21238
  if (allApproved) {
20967
21239
  get2(openedTest).status = "approved";
20968
- recalcSuiteStatuses(get2(tests), getTestPath(get2(openedTest)));
21240
+ recalcSuiteStatuses(tests, getTestPath(get2(openedTest)));
20969
21241
  }
20970
21242
  }
20971
21243
  return true;
@@ -20981,7 +21253,7 @@ function App($$anchor, $$props) {
20981
21253
  async function handleApproveAllTests() {
20982
21254
  const approvalResult = await $$props.onApproveAll();
20983
21255
  if (!isBulkApprovalOptimisticSafe(approvalResult)) return;
20984
- getAllTests(get2(tests)).forEach((test) => {
21256
+ getAllTests(tests).forEach((test) => {
20985
21257
  if (!test.results?.length) return;
20986
21258
  const lastIdx = test.results.length - 1;
20987
21259
  const lastResult = test.results[lastIdx];
@@ -20989,7 +21261,7 @@ function App($$anchor, $$props) {
20989
21261
  test.approved = Object.fromEntries(Object.keys(lastResult.images).map((name) => [name, lastIdx]));
20990
21262
  test.status = "approved";
20991
21263
  });
20992
- recalcAllSuiteStatuses(get2(tests));
21264
+ recalcAllSuiteStatuses(tests);
20993
21265
  }
20994
21266
  function handleStart() {
20995
21267
  }
@@ -21020,24 +21292,46 @@ function App($$anchor, $$props) {
21020
21292
  }
21021
21293
  const wsProtocol = location.protocol === "https:" ? "wss:" : "ws:";
21022
21294
  const ws = new WebSocket(`${wsProtocol}//${location.host}`);
21023
- let timer;
21024
- const refresh = async () => {
21295
+ const handleMessage = (event2) => {
21025
21296
  try {
21026
- const response = await fetch("/api/report");
21027
- const data = await response.json();
21028
- const newTree = treeifyTests(data.tests);
21029
- mergeTreeState(newTree, get2(tests));
21030
- set(tests, newTree, true);
21297
+ const raw = JSON.parse(event2.data);
21298
+ const msg = safeParse3(WebSocketMessageSchema, raw);
21299
+ if (msg === null) return;
21300
+ applyClientMessage(msg);
21031
21301
  } catch {
21032
21302
  }
21033
21303
  };
21034
- ws.onmessage = () => {
21035
- clearTimeout(timer);
21036
- timer = setTimeout(refresh, 50);
21304
+ const applyClientMessage = (msg) => {
21305
+ switch (msg.type) {
21306
+ case "test-begin":
21307
+ case "test-update": {
21308
+ const current = collectTestsById(tests);
21309
+ current[msg.data.id] = msg.data;
21310
+ syncTreeState(tests, current);
21311
+ break;
21312
+ }
21313
+ case "run-end": {
21314
+ if (msg.data.removedTestIds.length === 0) break;
21315
+ const current = collectTestsById(tests);
21316
+ const next2 = {};
21317
+ const removed = new Set(msg.data.removedTestIds);
21318
+ for (const [id, data] of Object.entries(current)) {
21319
+ if (!removed.has(id)) next2[id] = data;
21320
+ }
21321
+ syncTreeState(tests, next2);
21322
+ break;
21323
+ }
21324
+ case "sync": {
21325
+ syncTreeState(tests, msg.data.tests);
21326
+ break;
21327
+ }
21328
+ case "approve":
21329
+ break;
21330
+ }
21037
21331
  };
21332
+ ws.onmessage = handleMessage;
21038
21333
  return () => {
21039
21334
  ws.close();
21040
- clearTimeout(timer);
21041
21335
  };
21042
21336
  });
21043
21337
  var div = root8();
@@ -21046,7 +21340,7 @@ function App($$anchor, $$props) {
21046
21340
  let $0 = user_derived(() => get2(openedTest)?.id);
21047
21341
  Sidebar(node, {
21048
21342
  get tests() {
21049
- return get2(tests);
21343
+ return tests;
21050
21344
  },
21051
21345
  get selectedId() {
21052
21346
  return get2($0);
@@ -1 +1 @@
1
- {"version":3,"file":"report-utils.d.ts","sourceRoot":"","sources":["../src/report-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAChE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAMhE,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,CAUzD;AASD,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,kBAAkB,EAAE,SAAS,qBAAqB,EAAE,GAAG,SAAS,GAC/D,SAAS,MAAM,EAAE,CAEnB;AAED,wBAAgB,sBAAsB,CACpC,kBAAkB,EAAE,SAAS,qBAAqB,EAAE,GAAG,SAAS,GAC/D,SAAS,qBAAqB,EAAE,GAAG,SAAS,CAE9C;AAED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EACvC,WAAW,EAAE,SAAS,MAAM,EAAE,GAC7B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CASjC;AAED,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,UAAU,EAAE,EACzB,kBAAkB,SAAkB,GACnC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAgCjC;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAWrF"}
1
+ {"version":3,"file":"report-utils.d.ts","sourceRoot":"","sources":["../src/report-utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAChE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAMhE,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,CAUzD;AASD,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,kBAAkB,EAAE,SAAS,qBAAqB,EAAE,GAAG,SAAS,GAC/D,SAAS,MAAM,EAAE,CAEnB;AAED,wBAAgB,sBAAsB,CACpC,kBAAkB,EAAE,SAAS,qBAAqB,EAAE,GAAG,SAAS,GAC/D,SAAS,qBAAqB,EAAE,GAAG,SAAS,CAE9C;AAED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EACvC,WAAW,EAAE,SAAS,MAAM,EAAE,GAC7B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CASjC;AAED,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,UAAU,EAAE,EACzB,kBAAkB,SAAkB,GACnC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAkCjC;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAWrF"}
@@ -1,11 +1,9 @@
1
1
  import type { AttachmentData } from './reporter-utils.ts';
2
- import { resolveBaselineTargets } from './snapshot-path-resolver.ts';
2
+ import type { ResolvedBaselineTarget } from './snapshot-path-resolver.ts';
3
3
  export type RunEvent = {
4
4
  type: 'test-begin' | 'test-end' | 'run-end';
5
5
  data: unknown;
6
6
  };
7
- export type BaselineResolverInput = Parameters<typeof resolveBaselineTargets>[0];
8
- export type ResolvedBaselineTarget = ReturnType<typeof resolveBaselineTargets>[number];
9
7
  export declare function encodeArtifactPathSegment(segment: string): string;
10
8
  export declare function safeArtifactPath(name: string): string;
11
9
  export declare function sanitizeId(id: string): string;
@@ -1 +1 @@
1
- {"version":3,"file":"reporter-artifact-ops.d.ts","sourceRoot":"","sources":["../src/reporter-artifact-ops.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACzD,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAA;AAOpE,MAAM,MAAM,QAAQ,GAAG;IAAE,IAAI,EAAE,YAAY,GAAG,UAAU,GAAG,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAA;AACrF,MAAM,MAAM,qBAAqB,GAAG,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAA;AAChF,MAAM,MAAM,sBAAsB,GAAG,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEtF,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAMjE;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,EAClB,iBAAiB,EAAE,MAAM,EACzB,MAAM,EAAE,sBAAsB,EAC9B,gBAAgB,EAAE,cAAc,EAAE,GACjC,OAAO,CAAC,IAAI,CAAC,CAUf;AAED,wBAAsB,eAAe,CACnC,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE;IAAE,WAAW,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAAE,GAC/E,OAAO,CAAC,cAAc,EAAE,CAAC,CAoC3B;AAED,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,QAAQ,EAAE,EACrB,iBAAiB,EAAE,MAAM,EACzB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAiBf;AAED,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,QAAQ,EAAE,EACrB,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,IAAI,CAAC,CAOf"}
1
+ {"version":3,"file":"reporter-artifact-ops.d.ts","sourceRoot":"","sources":["../src/reporter-artifact-ops.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACzD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAA;AAOzE,MAAM,MAAM,QAAQ,GAAG;IAAE,IAAI,EAAE,YAAY,GAAG,UAAU,GAAG,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAA;AAErF,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAMjE;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,EAClB,iBAAiB,EAAE,MAAM,EACzB,MAAM,EAAE,sBAAsB,EAC9B,gBAAgB,EAAE,cAAc,EAAE,GACjC,OAAO,CAAC,IAAI,CAAC,CAUf;AAED,wBAAsB,eAAe,CACnC,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE;IAAE,WAAW,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAAE,GAC/E,OAAO,CAAC,cAAc,EAAE,CAAC,CAoC3B;AAED,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,QAAQ,EAAE,EACrB,iBAAiB,EAAE,MAAM,EACzB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAiBf;AAED,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,QAAQ,EAAE,EACrB,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,IAAI,CAAC,CAOf"}