@buoy-gg/assets 7.0.41 → 7.0.43
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/README.md +12 -73
- package/bundlers/vite.mjs +1 -53
- package/lib/commonjs/autoCapture.js +1 -5
- package/lib/commonjs/autoCapture.web.js +1 -6
- package/lib/commonjs/capture/assetLifecycle.js +1 -11
- package/lib/commonjs/capture/assetLifecycle.web.js +1 -20
- package/lib/commonjs/capture/browserAssets.js +1 -83
- package/lib/commonjs/capture/expo.js +1 -46
- package/lib/commonjs/capture/expo.web.js +1 -19
- package/lib/commonjs/capture/install.js +1 -91
- package/lib/commonjs/capture/install.web.js +1 -75
- package/lib/commonjs/capture/isBuoyAsset.js +1 -23
- package/lib/commonjs/capture/measure.js +1 -187
- package/lib/commonjs/capture/metroGraph.js +1 -175
- package/lib/commonjs/capture/metroGraph.web.js +1 -27
- package/lib/commonjs/capture/registry.js +1 -127
- package/lib/commonjs/components/AssetDetail.js +1 -211
- package/lib/commonjs/components/AssetImage.js +1 -66
- package/lib/commonjs/components/AssetRow.js +1 -232
- package/lib/commonjs/components/AssetsFilterBar.js +1 -105
- package/lib/commonjs/components/AssetsHeader.js +1 -73
- package/lib/commonjs/components/AssetsIcon.js +1 -19
- package/lib/commonjs/components/AssetsListControls.js +1 -81
- package/lib/commonjs/components/AssetsModal.js +1 -378
- package/lib/commonjs/components/ScanNotice.js +1 -86
- package/lib/commonjs/components/optionalExpoImage.js +1 -16
- package/lib/commonjs/components/optionalExpoImage.web.js +1 -10
- package/lib/commonjs/export.js +1 -62
- package/lib/commonjs/format.js +1 -59
- package/lib/commonjs/hooks/useAssetRecords.js +1 -22
- package/lib/commonjs/hooks/useAssetScanStatus.js +1 -28
- package/lib/commonjs/index.js +1 -294
- package/lib/commonjs/preset.js +1 -39
- package/lib/commonjs/remote.js +1 -68
- package/lib/commonjs/state.js +1 -51
- package/lib/commonjs/store/assetStore.js +1 -256
- package/lib/commonjs/store/baseline.js +1 -175
- package/lib/commonjs/store/insights.js +1 -120
- package/lib/commonjs/sync/assetsSyncAdapter.js +1 -157
- package/lib/commonjs/types.js +0 -1
- package/lib/commonjs/utils/deviceScale.js +1 -50
- package/lib/commonjs/utils/testIDs.js +1 -110
- package/lib/module/autoCapture.js +1 -5
- package/lib/module/autoCapture.web.js +1 -5
- package/lib/module/capture/assetLifecycle.js +1 -7
- package/lib/module/capture/assetLifecycle.web.js +1 -16
- package/lib/module/capture/browserAssets.js +1 -75
- package/lib/module/capture/expo.js +1 -41
- package/lib/module/capture/expo.web.js +1 -14
- package/lib/module/capture/install.js +1 -85
- package/lib/module/capture/install.web.js +1 -68
- package/lib/module/capture/isBuoyAsset.js +1 -18
- package/lib/module/capture/measure.js +1 -180
- package/lib/module/capture/metroGraph.js +1 -168
- package/lib/module/capture/metroGraph.web.js +1 -21
- package/lib/module/capture/registry.js +1 -120
- package/lib/module/components/AssetDetail.js +1 -207
- package/lib/module/components/AssetImage.js +1 -62
- package/lib/module/components/AssetRow.js +1 -228
- package/lib/module/components/AssetsFilterBar.js +1 -101
- package/lib/module/components/AssetsHeader.js +1 -69
- package/lib/module/components/AssetsIcon.js +1 -12
- package/lib/module/components/AssetsListControls.js +1 -77
- package/lib/module/components/AssetsModal.js +1 -373
- package/lib/module/components/ScanNotice.js +1 -82
- package/lib/module/components/optionalExpoImage.js +1 -12
- package/lib/module/components/optionalExpoImage.web.js +1 -6
- package/lib/module/export.js +1 -57
- package/lib/module/format.js +1 -51
- package/lib/module/hooks/useAssetRecords.js +1 -18
- package/lib/module/hooks/useAssetScanStatus.js +1 -23
- package/lib/module/index.js +1 -64
- package/lib/module/preset.js +1 -35
- package/lib/module/remote.js +1 -61
- package/lib/module/state.js +1 -47
- package/lib/module/store/assetStore.js +1 -240
- package/lib/module/store/baseline.js +1 -167
- package/lib/module/store/insights.js +1 -116
- package/lib/module/sync/assetsSyncAdapter.js +1 -153
- package/lib/module/types.js +0 -1
- package/lib/module/utils/deviceScale.js +1 -46
- package/lib/module/utils/testIDs.js +1 -99
- package/lib/web/index.mjs +3 -4
- package/package.json +3 -8
- package/lib/commonjs/autoCapture.js.map +0 -1
- package/lib/commonjs/autoCapture.web.js.map +0 -1
- package/lib/commonjs/capture/assetLifecycle.js.map +0 -1
- package/lib/commonjs/capture/assetLifecycle.web.js.map +0 -1
- package/lib/commonjs/capture/browserAssets.js.map +0 -1
- package/lib/commonjs/capture/expo.js.map +0 -1
- package/lib/commonjs/capture/expo.web.js.map +0 -1
- package/lib/commonjs/capture/install.js.map +0 -1
- package/lib/commonjs/capture/install.web.js.map +0 -1
- package/lib/commonjs/capture/isBuoyAsset.js.map +0 -1
- package/lib/commonjs/capture/measure.js.map +0 -1
- package/lib/commonjs/capture/metroGraph.js.map +0 -1
- package/lib/commonjs/capture/metroGraph.web.js.map +0 -1
- package/lib/commonjs/capture/registry.js.map +0 -1
- package/lib/commonjs/components/AssetDetail.js.map +0 -1
- package/lib/commonjs/components/AssetImage.js.map +0 -1
- package/lib/commonjs/components/AssetRow.js.map +0 -1
- package/lib/commonjs/components/AssetsFilterBar.js.map +0 -1
- package/lib/commonjs/components/AssetsHeader.js.map +0 -1
- package/lib/commonjs/components/AssetsIcon.js.map +0 -1
- package/lib/commonjs/components/AssetsListControls.js.map +0 -1
- package/lib/commonjs/components/AssetsModal.js.map +0 -1
- package/lib/commonjs/components/ScanNotice.js.map +0 -1
- package/lib/commonjs/components/optionalExpoImage.js.map +0 -1
- package/lib/commonjs/components/optionalExpoImage.web.js.map +0 -1
- package/lib/commonjs/export.js.map +0 -1
- package/lib/commonjs/format.js.map +0 -1
- package/lib/commonjs/hooks/useAssetRecords.js.map +0 -1
- package/lib/commonjs/hooks/useAssetScanStatus.js.map +0 -1
- package/lib/commonjs/index.js.map +0 -1
- package/lib/commonjs/preset.js.map +0 -1
- package/lib/commonjs/remote.js.map +0 -1
- package/lib/commonjs/state.js.map +0 -1
- package/lib/commonjs/store/assetStore.js.map +0 -1
- package/lib/commonjs/store/baseline.js.map +0 -1
- package/lib/commonjs/store/insights.js.map +0 -1
- package/lib/commonjs/sync/assetsSyncAdapter.js.map +0 -1
- package/lib/commonjs/types.js.map +0 -1
- package/lib/commonjs/utils/deviceScale.js.map +0 -1
- package/lib/commonjs/utils/testIDs.js.map +0 -1
- package/lib/module/autoCapture.js.map +0 -1
- package/lib/module/autoCapture.web.js.map +0 -1
- package/lib/module/capture/assetLifecycle.js.map +0 -1
- package/lib/module/capture/assetLifecycle.web.js.map +0 -1
- package/lib/module/capture/browserAssets.js.map +0 -1
- package/lib/module/capture/expo.js.map +0 -1
- package/lib/module/capture/expo.web.js.map +0 -1
- package/lib/module/capture/install.js.map +0 -1
- package/lib/module/capture/install.web.js.map +0 -1
- package/lib/module/capture/isBuoyAsset.js.map +0 -1
- package/lib/module/capture/measure.js.map +0 -1
- package/lib/module/capture/metroGraph.js.map +0 -1
- package/lib/module/capture/metroGraph.web.js.map +0 -1
- package/lib/module/capture/registry.js.map +0 -1
- package/lib/module/components/AssetDetail.js.map +0 -1
- package/lib/module/components/AssetImage.js.map +0 -1
- package/lib/module/components/AssetRow.js.map +0 -1
- package/lib/module/components/AssetsFilterBar.js.map +0 -1
- package/lib/module/components/AssetsHeader.js.map +0 -1
- package/lib/module/components/AssetsIcon.js.map +0 -1
- package/lib/module/components/AssetsListControls.js.map +0 -1
- package/lib/module/components/AssetsModal.js.map +0 -1
- package/lib/module/components/ScanNotice.js.map +0 -1
- package/lib/module/components/optionalExpoImage.js.map +0 -1
- package/lib/module/components/optionalExpoImage.web.js.map +0 -1
- package/lib/module/export.js.map +0 -1
- package/lib/module/format.js.map +0 -1
- package/lib/module/hooks/useAssetRecords.js.map +0 -1
- package/lib/module/hooks/useAssetScanStatus.js.map +0 -1
- package/lib/module/index.js.map +0 -1
- package/lib/module/preset.js.map +0 -1
- package/lib/module/remote.js.map +0 -1
- package/lib/module/state.js.map +0 -1
- package/lib/module/store/assetStore.js.map +0 -1
- package/lib/module/store/baseline.js.map +0 -1
- package/lib/module/store/insights.js.map +0 -1
- package/lib/module/sync/assetsSyncAdapter.js.map +0 -1
- package/lib/module/types.js.map +0 -1
- package/lib/module/utils/deviceScale.js.map +0 -1
- package/lib/module/utils/testIDs.js.map +0 -1
- package/lib/typescript/autoCapture.d.ts.map +0 -1
- package/lib/typescript/autoCapture.web.d.ts.map +0 -1
- package/lib/typescript/capture/assetLifecycle.d.ts.map +0 -1
- package/lib/typescript/capture/assetLifecycle.web.d.ts.map +0 -1
- package/lib/typescript/capture/browserAssets.d.ts.map +0 -1
- package/lib/typescript/capture/expo.d.ts.map +0 -1
- package/lib/typescript/capture/expo.web.d.ts.map +0 -1
- package/lib/typescript/capture/install.d.ts.map +0 -1
- package/lib/typescript/capture/install.web.d.ts.map +0 -1
- package/lib/typescript/capture/isBuoyAsset.d.ts.map +0 -1
- package/lib/typescript/capture/measure.d.ts.map +0 -1
- package/lib/typescript/capture/metroGraph.d.ts.map +0 -1
- package/lib/typescript/capture/metroGraph.web.d.ts.map +0 -1
- package/lib/typescript/capture/registry.d.ts.map +0 -1
- package/lib/typescript/components/AssetDetail.d.ts.map +0 -1
- package/lib/typescript/components/AssetImage.d.ts.map +0 -1
- package/lib/typescript/components/AssetRow.d.ts.map +0 -1
- package/lib/typescript/components/AssetsFilterBar.d.ts.map +0 -1
- package/lib/typescript/components/AssetsHeader.d.ts.map +0 -1
- package/lib/typescript/components/AssetsIcon.d.ts.map +0 -1
- package/lib/typescript/components/AssetsListControls.d.ts.map +0 -1
- package/lib/typescript/components/AssetsModal.d.ts.map +0 -1
- package/lib/typescript/components/ScanNotice.d.ts.map +0 -1
- package/lib/typescript/components/optionalExpoImage.d.ts.map +0 -1
- package/lib/typescript/components/optionalExpoImage.web.d.ts.map +0 -1
- package/lib/typescript/export.d.ts.map +0 -1
- package/lib/typescript/format.d.ts.map +0 -1
- package/lib/typescript/hooks/useAssetRecords.d.ts.map +0 -1
- package/lib/typescript/hooks/useAssetScanStatus.d.ts.map +0 -1
- package/lib/typescript/index.d.ts.map +0 -1
- package/lib/typescript/preset.d.ts.map +0 -1
- package/lib/typescript/remote.d.ts.map +0 -1
- package/lib/typescript/state.d.ts.map +0 -1
- package/lib/typescript/store/assetStore.d.ts.map +0 -1
- package/lib/typescript/store/baseline.d.ts.map +0 -1
- package/lib/typescript/store/insights.d.ts.map +0 -1
- package/lib/typescript/sync/assetsSyncAdapter.d.ts.map +0 -1
- package/lib/typescript/types.d.ts.map +0 -1
- package/lib/typescript/utils/deviceScale.d.ts.map +0 -1
- package/lib/typescript/utils/testIDs.d.ts.map +0 -1
|
@@ -1,167 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Baseline diff — the regression-prevention half of the tool.
|
|
5
|
-
*
|
|
6
|
-
* "Save baseline" persists a slim {hash, size} snapshot of the inventory via
|
|
7
|
-
* shared-ui's persistentStorage (FileSystem → AsyncStorage → memory), and
|
|
8
|
-
* every later scan diffs against it: new assets, removed assets, grown
|
|
9
|
-
* assets, net bytes. Run it before and after a branch and you get "this
|
|
10
|
-
* branch added 2.3 MB" without any CI tooling.
|
|
11
|
-
*
|
|
12
|
-
* On dashboard mirrors the actions forward to the device (the baseline
|
|
13
|
-
* belongs to the app being inspected, not the dashboard) and the computed
|
|
14
|
-
* diff arrives back inside the synced snapshot.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { persistentStorage } from "@buoy-gg/shared-ui";
|
|
18
|
-
import { remoteAssetsDispatch } from "../remote";
|
|
19
|
-
import { assetsState } from "../state";
|
|
20
|
-
import { getSnapshot, invalidate } from "./assetStore";
|
|
21
|
-
import { isBuoyAssetPath } from "../capture/isBuoyAsset";
|
|
22
|
-
const STORAGE_KEY = "@react_buoy/assets/baseline";
|
|
23
|
-
|
|
24
|
-
/** ≥1 KB counts as growth; below that is codec/metadata noise. */
|
|
25
|
-
const GROWN_THRESHOLD_BYTES = 1024;
|
|
26
|
-
|
|
27
|
-
/** Load the persisted baseline into state once; safe to call repeatedly. */
|
|
28
|
-
export async function loadBaseline() {
|
|
29
|
-
const s = assetsState();
|
|
30
|
-
if (s.baselineLoaded) return s.baseline;
|
|
31
|
-
s.baselineLoaded = true;
|
|
32
|
-
try {
|
|
33
|
-
const raw = await persistentStorage.getItem(STORAGE_KEY);
|
|
34
|
-
if (raw) {
|
|
35
|
-
const parsed = JSON.parse(raw);
|
|
36
|
-
if (parsed && typeof parsed.savedAt === "number" && parsed.entries) {
|
|
37
|
-
s.baseline = parsed;
|
|
38
|
-
invalidate();
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
} catch {
|
|
42
|
-
// unreadable baseline = no baseline
|
|
43
|
-
}
|
|
44
|
-
return s.baseline;
|
|
45
|
-
}
|
|
46
|
-
export async function saveBaseline() {
|
|
47
|
-
const remote = remoteAssetsDispatch();
|
|
48
|
-
if (remote) {
|
|
49
|
-
await remote("saveBaseline");
|
|
50
|
-
return assetsState().baseline ?? {
|
|
51
|
-
savedAt: Date.now(),
|
|
52
|
-
entries: {}
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
const s = assetsState();
|
|
56
|
-
const entries = {};
|
|
57
|
-
for (const r of getSnapshot()) {
|
|
58
|
-
entries[r.key] = {
|
|
59
|
-
hash: r.hash ?? null,
|
|
60
|
-
sizeBytes: r.sizeSource === "measured" ? r.sizeBytes ?? null : null,
|
|
61
|
-
name: r.name,
|
|
62
|
-
type: r.type
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
const baseline = {
|
|
66
|
-
savedAt: Date.now(),
|
|
67
|
-
entries
|
|
68
|
-
};
|
|
69
|
-
s.baseline = baseline;
|
|
70
|
-
s.baselineLoaded = true;
|
|
71
|
-
invalidate();
|
|
72
|
-
try {
|
|
73
|
-
await persistentStorage.setItem(STORAGE_KEY, JSON.stringify(baseline));
|
|
74
|
-
} catch {
|
|
75
|
-
// in-memory baseline still works for this session
|
|
76
|
-
}
|
|
77
|
-
return baseline;
|
|
78
|
-
}
|
|
79
|
-
export async function clearBaseline() {
|
|
80
|
-
const remote = remoteAssetsDispatch();
|
|
81
|
-
if (remote) {
|
|
82
|
-
await remote("clearBaseline");
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
const s = assetsState();
|
|
86
|
-
s.baseline = null;
|
|
87
|
-
s.baselineLoaded = true;
|
|
88
|
-
invalidate();
|
|
89
|
-
try {
|
|
90
|
-
await persistentStorage.removeItem(STORAGE_KEY);
|
|
91
|
-
} catch {
|
|
92
|
-
// nothing to clean up
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
export function computeDiff(records, baseline) {
|
|
96
|
-
const added = [];
|
|
97
|
-
const grown = [];
|
|
98
|
-
const seen = new Set();
|
|
99
|
-
let currentBytes = 0;
|
|
100
|
-
let baselineBytes = 0;
|
|
101
|
-
let comparable = false;
|
|
102
|
-
for (const r of records) {
|
|
103
|
-
seen.add(r.key);
|
|
104
|
-
const then = baseline.entries[r.key];
|
|
105
|
-
if (!then) {
|
|
106
|
-
added.push(r.id);
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
const now = r.sizeSource === "measured" ? r.sizeBytes ?? null : null;
|
|
110
|
-
if (now != null && then.sizeBytes != null) {
|
|
111
|
-
comparable = true;
|
|
112
|
-
currentBytes += now;
|
|
113
|
-
baselineBytes += then.sizeBytes;
|
|
114
|
-
if (now - then.sizeBytes >= GROWN_THRESHOLD_BYTES) {
|
|
115
|
-
grown.push({
|
|
116
|
-
id: r.id,
|
|
117
|
-
deltaBytes: now - then.sizeBytes
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
const removed = [];
|
|
123
|
-
for (const [key, entry] of Object.entries(baseline.entries)) {
|
|
124
|
-
if (isBuoyAssetPath(key)) continue;
|
|
125
|
-
if (!seen.has(key)) {
|
|
126
|
-
removed.push({
|
|
127
|
-
key,
|
|
128
|
-
name: entry.name,
|
|
129
|
-
type: entry.type
|
|
130
|
-
});
|
|
131
|
-
if (entry.sizeBytes != null) {
|
|
132
|
-
// A removal is as comparable as a survivor: its bytes were measured
|
|
133
|
-
// then and are definitionally zero now. Counting them toward the
|
|
134
|
-
// baseline total without marking the diff comparable meant a branch
|
|
135
|
-
// that only DELETED assets reported "12 removed" and no size at all —
|
|
136
|
-
// the one number anyone reads that bar for.
|
|
137
|
-
comparable = true;
|
|
138
|
-
baselineBytes += entry.sizeBytes;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
// Added assets' measured bytes count toward the net even without a
|
|
143
|
-
// baseline counterpart — that's exactly the growth being reported.
|
|
144
|
-
for (const id of added) {
|
|
145
|
-
const r = records.find(rec => rec.id === id);
|
|
146
|
-
if (r?.sizeSource === "measured" && r.sizeBytes != null) {
|
|
147
|
-
comparable = true;
|
|
148
|
-
currentBytes += r.sizeBytes;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
return {
|
|
152
|
-
baselineSavedAt: baseline.savedAt,
|
|
153
|
-
added,
|
|
154
|
-
removed,
|
|
155
|
-
grown: grown.sort((a, b) => b.deltaBytes - a.deltaBytes),
|
|
156
|
-
netBytes: comparable ? currentBytes - baselineBytes : null
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/** Current diff: the synced device's on mirrors, computed locally on device. */
|
|
161
|
-
export function getDiff() {
|
|
162
|
-
const s = assetsState();
|
|
163
|
-
if (s.mirrorDiff) return s.mirrorDiff;
|
|
164
|
-
if (!s.baseline) return null;
|
|
165
|
-
return computeDiff(getSnapshot(), s.baseline);
|
|
166
|
-
}
|
|
167
|
-
//# sourceMappingURL=baseline.js.map
|
|
1
|
+
"use strict";import{persistentStorage as y}from"@buoy-gg/shared-ui";import{remoteAssetsDispatch as B}from"../remote";import{assetsState as l}from"../state";import{getSnapshot as b,invalidate as d}from"./assetStore";import{isBuoyAssetPath as h}from"../capture/isBuoyAsset";const m="@react_buoy/assets/baseline";const z=1024;export async function loadBaseline(){const e=l();if(e.baselineLoaded)return e.baseline;e.baselineLoaded=true;try{const n=await y.getItem(m);if(n){const i=JSON.parse(n);if(i&&typeof i.savedAt==="number"&&i.entries){e.baseline=i;d()}}}catch{}return e.baseline}export async function saveBaseline(){const e=B();if(e){await e("saveBaseline");return l().baseline??{savedAt:Date.now(),entries:{}}}const n=l();const i={};for(const r of b()){i[r.key]={hash:r.hash??null,sizeBytes:r.sizeSource==="measured"?r.sizeBytes??null:null,name:r.name,type:r.type}}const a={savedAt:Date.now(),entries:i};n.baseline=a;n.baselineLoaded=true;d();try{await y.setItem(m,JSON.stringify(a))}catch{}return a}export async function clearBaseline(){const e=B();if(e){await e("clearBaseline");return}const n=l();n.baseline=null;n.baselineLoaded=true;d();try{await y.removeItem(m)}catch{}}export function computeDiff(e,n){const i=[];const a=[];const r=new Set;let u=0;let f=0;let c=false;for(const s of e){r.add(s.key);const t=n.entries[s.key];if(!t){i.push(s.id);continue}const o=s.sizeSource==="measured"?s.sizeBytes??null:null;if(o!=null&&t.sizeBytes!=null){c=true;u+=o;f+=t.sizeBytes;if(o-t.sizeBytes>=z){a.push({id:s.id,deltaBytes:o-t.sizeBytes})}}}const p=[];for(const[s,t]of Object.entries(n.entries)){if(h(s))continue;if(!r.has(s)){p.push({key:s,name:t.name,type:t.type});if(t.sizeBytes!=null){c=true;f+=t.sizeBytes}}}for(const s of i){const t=e.find(o=>o.id===s);if(t?.sizeSource==="measured"&&t.sizeBytes!=null){c=true;u+=t.sizeBytes}}return{baselineSavedAt:n.savedAt,added:i,removed:p,grown:a.sort((s,t)=>t.deltaBytes-s.deltaBytes),netBytes:c?u-f:null}}export function getDiff(){const e=l();if(e.mirrorDiff)return e.mirrorDiff;if(!e.baseline)return null;return computeDiff(b(),e.baseline)}
|
|
@@ -1,116 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Derived insights over the asset inventory — the Lighthouse/Emerge-style
|
|
5
|
-
* audit findings, each pointing at concrete record ids.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { estDecodedBytes } from "./assetStore";
|
|
9
|
-
import { effectivePixelRatio } from "../utils/deviceScale";
|
|
10
|
-
/** png/jpeg at or above this measured size are flagged as WebP candidates. */
|
|
11
|
-
const WEBP_CANDIDATE_BYTES = 50 * 1024;
|
|
12
|
-
/** GIFs at or above this measured size decode into serious frame memory. */
|
|
13
|
-
const HEAVY_GIF_BYTES = 100 * 1024;
|
|
14
|
-
/** Fonts at or above this are usually unsubset icon fonts. */
|
|
15
|
-
const HEAVY_FONT_BYTES = 150 * 1024;
|
|
16
|
-
/** Flag images decoding to more pixels than this many screenfuls. */
|
|
17
|
-
const OVER_DECODE_SCREENS = 2;
|
|
18
|
-
/** A higher scale variant this much smaller than a lower one = bad export. */
|
|
19
|
-
const SCALE_ANOMALY_RATIO = 0.9;
|
|
20
|
-
/**
|
|
21
|
-
* Anomaly checks only apply when the smaller variant is at least this big —
|
|
22
|
-
* for tiny icons PNG overhead dominates and byte ordering is pure noise.
|
|
23
|
-
*/
|
|
24
|
-
const SCALE_ANOMALY_MIN_BYTES = 4 * 1024;
|
|
25
|
-
|
|
26
|
-
/** Physical pixels of the entire screen (fallback ≈ a 6.1" phone). */
|
|
27
|
-
function screenPixels() {
|
|
28
|
-
try {
|
|
29
|
-
const {
|
|
30
|
-
Dimensions,
|
|
31
|
-
PixelRatio
|
|
32
|
-
} = require("react-native");
|
|
33
|
-
const screen = Dimensions.get("screen");
|
|
34
|
-
const ratio = PixelRatio.get();
|
|
35
|
-
return screen.width * ratio * screen.height * ratio;
|
|
36
|
-
} catch {
|
|
37
|
-
return 1170 * 2532;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** True when the measured per-scale sizes contradict the scales (bad export). */
|
|
42
|
-
function hasScaleAnomaly(record) {
|
|
43
|
-
const sizes = record.sizesByScale;
|
|
44
|
-
if (!sizes) return false;
|
|
45
|
-
const ordered = Object.entries(sizes).map(([scale, bytes]) => ({
|
|
46
|
-
scale: Number(scale),
|
|
47
|
-
bytes
|
|
48
|
-
})).filter(e => Number.isFinite(e.scale) && e.bytes > 0).sort((a, b) => a.scale - b.scale);
|
|
49
|
-
for (let i = 1; i < ordered.length; i++) {
|
|
50
|
-
if (ordered[i - 1].bytes >= SCALE_ANOMALY_MIN_BYTES && ordered[i].bytes < ordered[i - 1].bytes * SCALE_ANOMALY_RATIO) {
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
export function computeInsights(list) {
|
|
57
|
-
const deviceScale = effectivePixelRatio();
|
|
58
|
-
const byHash = new Map();
|
|
59
|
-
for (const r of list) {
|
|
60
|
-
if (!r.hash) continue;
|
|
61
|
-
const group = byHash.get(r.hash);
|
|
62
|
-
if (group) group.push(r);else byHash.set(r.hash, [r]);
|
|
63
|
-
}
|
|
64
|
-
const duplicates = [];
|
|
65
|
-
for (const [hash, group] of byHash) {
|
|
66
|
-
if (group.length < 2) continue;
|
|
67
|
-
const measured = group.find(r => r.sizeBytes != null);
|
|
68
|
-
duplicates.push({
|
|
69
|
-
hash,
|
|
70
|
-
ids: group.map(r => r.id),
|
|
71
|
-
wastedBytes: measured?.sizeBytes != null ? measured.sizeBytes * (group.length - 1) : null
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
const screenPx = screenPixels();
|
|
75
|
-
const scaleGaps = [];
|
|
76
|
-
const webpCandidates = [];
|
|
77
|
-
const neverLoaded = [];
|
|
78
|
-
const overDecode = [];
|
|
79
|
-
const heavyGifs = [];
|
|
80
|
-
const heavyFonts = [];
|
|
81
|
-
const scaleAnomalies = [];
|
|
82
|
-
for (const r of list) {
|
|
83
|
-
if (!r.loaded) neverLoaded.push(r.id);
|
|
84
|
-
// Only multi-scale assets missing the device scale are flagged — the
|
|
85
|
-
// author clearly manages variants and missed one. Single-@1x images are
|
|
86
|
-
// the overwhelmingly common case and flagging them all is pure noise.
|
|
87
|
-
if (r.kind === "image" && r.scales.length > 1 && !r.scales.some(s => s >= deviceScale)) {
|
|
88
|
-
scaleGaps.push(r.id);
|
|
89
|
-
}
|
|
90
|
-
if ((r.type === "png" || r.type === "jpg" || r.type === "jpeg") && r.sizeBytes != null && r.sizeBytes >= WEBP_CANDIDATE_BYTES) {
|
|
91
|
-
webpCandidates.push(r.id);
|
|
92
|
-
}
|
|
93
|
-
// estDecodedBytes = pixels × 4, so pixels = bytes / 4.
|
|
94
|
-
if (r.kind === "image" && estDecodedBytes(r) / 4 > screenPx * OVER_DECODE_SCREENS) {
|
|
95
|
-
overDecode.push(r.id);
|
|
96
|
-
}
|
|
97
|
-
if (r.type === "gif" && (r.sizeBytes ?? 0) >= HEAVY_GIF_BYTES) {
|
|
98
|
-
heavyGifs.push(r.id);
|
|
99
|
-
}
|
|
100
|
-
if (r.kind === "font" && (r.sizeBytes ?? 0) >= HEAVY_FONT_BYTES) {
|
|
101
|
-
heavyFonts.push(r.id);
|
|
102
|
-
}
|
|
103
|
-
if (hasScaleAnomaly(r)) scaleAnomalies.push(r.id);
|
|
104
|
-
}
|
|
105
|
-
return {
|
|
106
|
-
duplicates,
|
|
107
|
-
scaleGaps,
|
|
108
|
-
webpCandidates,
|
|
109
|
-
neverLoaded,
|
|
110
|
-
overDecode,
|
|
111
|
-
heavyGifs,
|
|
112
|
-
heavyFonts,
|
|
113
|
-
scaleAnomalies
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
//# sourceMappingURL=insights.js.map
|
|
1
|
+
"use strict";import{estDecodedBytes as g}from"./assetStore";import{effectivePixelRatio as m}from"../utils/deviceScale";const B=50*1024;const E=100*1024;const A=150*1024;const _=2;const S=.9;const b=4*1024;function z(){try{const{Dimensions:i,PixelRatio:o}=require("react-native");const t=i.get("screen");const s=o.get();return t.width*s*t.height*s}catch{return 1170*2532}}function N(i){const o=i.sizesByScale;if(!o)return false;const t=Object.entries(o).map(([s,c])=>({scale:Number(s),bytes:c})).filter(s=>Number.isFinite(s.scale)&&s.bytes>0).sort((s,c)=>s.scale-c.scale);for(let s=1;s<t.length;s++){if(t[s-1].bytes>=b&&t[s].bytes<t[s-1].bytes*S){return true}}return false}export function computeInsights(i){const o=m();const t=new Map;for(const e of i){if(!e.hash)continue;const n=t.get(e.hash);if(n)n.push(e);else t.set(e.hash,[e])}const s=[];for(const[e,n]of t){if(n.length<2)continue;const y=n.find(r=>r.sizeBytes!=null);s.push({hash:e,ids:n.map(r=>r.id),wastedBytes:y?.sizeBytes!=null?y.sizeBytes*(n.length-1):null})}const c=z();const a=[];const l=[];const u=[];const f=[];const h=[];const d=[];const p=[];for(const e of i){if(!e.loaded)u.push(e.id);if(e.kind==="image"&&e.scales.length>1&&!e.scales.some(n=>n>=o)){a.push(e.id)}if((e.type==="png"||e.type==="jpg"||e.type==="jpeg")&&e.sizeBytes!=null&&e.sizeBytes>=B){l.push(e.id)}if(e.kind==="image"&&g(e)/4>c*_){f.push(e.id)}if(e.type==="gif"&&(e.sizeBytes??0)>=E){h.push(e.id)}if(e.kind==="font"&&(e.sizeBytes??0)>=A){d.push(e.id)}if(N(e))p.push(e.id)}return{duplicates:s,scaleGaps:a,webpCandidates:l,neverLoaded:u,overDecode:f,heavyGifs:h,heavyFonts:d,scaleAnomalies:p}}
|
|
@@ -1,153 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Sync adapter for the Assets tool, consumed by @buoy-gg/external-sync's
|
|
5
|
-
* `useExternalSync` (structurally matches its ToolSyncAdapter interface so
|
|
6
|
-
* this package doesn't need a dependency on it — same pattern as images).
|
|
7
|
-
* Exposes the asset inventory to the desktop dashboard and the MCP server.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { getScanStatus, rescan } from "../capture/install";
|
|
11
|
-
import { measureSizes } from "../capture/measure";
|
|
12
|
-
import { clearRecords, computeStats, estDecodedBytes, getRecord, getSnapshot, subscribe } from "../store/assetStore";
|
|
13
|
-
import { clearBaseline, getDiff, saveBaseline } from "../store/baseline";
|
|
14
|
-
import { computeInsights } from "../store/insights";
|
|
15
|
-
const KINDS = ["image", "font", "video", "audio", "data", "other"];
|
|
16
|
-
function toWire(record) {
|
|
17
|
-
return {
|
|
18
|
-
id: record.id,
|
|
19
|
-
key: record.key,
|
|
20
|
-
registryId: record.registryId ?? null,
|
|
21
|
-
name: record.name,
|
|
22
|
-
type: record.type,
|
|
23
|
-
kind: record.kind,
|
|
24
|
-
hash: record.hash ?? null,
|
|
25
|
-
location: record.httpServerLocation ?? null,
|
|
26
|
-
width: record.width ?? null,
|
|
27
|
-
height: record.height ?? null,
|
|
28
|
-
scales: record.scales,
|
|
29
|
-
uri: record.resolvedUri ?? null,
|
|
30
|
-
loaded: record.loaded,
|
|
31
|
-
sizeBytes: record.sizeSource === "measured" ? record.sizeBytes ?? null : estDecodedBytes(record) || null,
|
|
32
|
-
sizeSource: record.sizeSource ?? null
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
function requireRecord(params) {
|
|
36
|
-
const id = params?.id;
|
|
37
|
-
if (typeof id !== "number") throw new Error("Missing numeric `id` param");
|
|
38
|
-
const record = getRecord(id);
|
|
39
|
-
if (!record) {
|
|
40
|
-
throw new Error(`Asset record ${id} not found — re-run the list action for current ids.`);
|
|
41
|
-
}
|
|
42
|
-
return record;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** Largest-first is the useful default for an inventory. */
|
|
46
|
-
function bySizeDesc(a, b) {
|
|
47
|
-
return (b.sizeBytes ?? estDecodedBytes(b)) - (a.sizeBytes ?? estDecodedBytes(a));
|
|
48
|
-
}
|
|
49
|
-
function overview() {
|
|
50
|
-
const snapshot = getSnapshot();
|
|
51
|
-
return {
|
|
52
|
-
stats: computeStats(snapshot),
|
|
53
|
-
scanStatus: getScanStatus(),
|
|
54
|
-
insights: computeInsights(snapshot),
|
|
55
|
-
diff: getDiff(),
|
|
56
|
-
records: [...snapshot].sort(bySizeDesc)
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
export const assetsSyncAdapter = {
|
|
60
|
-
version: 1,
|
|
61
|
-
getSnapshot: () => {
|
|
62
|
-
const {
|
|
63
|
-
records,
|
|
64
|
-
...rest
|
|
65
|
-
} = overview();
|
|
66
|
-
return {
|
|
67
|
-
...rest,
|
|
68
|
-
records: records.slice(0, 200).map(toWire)
|
|
69
|
-
};
|
|
70
|
-
},
|
|
71
|
-
subscribe: onChange => subscribe(onChange),
|
|
72
|
-
actions: {
|
|
73
|
-
/** List assets, largest first. Params: { limit?, kind?, loadedOnly? }. */
|
|
74
|
-
list: params => {
|
|
75
|
-
const p = params ?? {};
|
|
76
|
-
const limit = Math.min(Math.max(p.limit ?? 50, 1), 500);
|
|
77
|
-
const {
|
|
78
|
-
records,
|
|
79
|
-
...rest
|
|
80
|
-
} = overview();
|
|
81
|
-
let filtered = records;
|
|
82
|
-
if (p.kind) {
|
|
83
|
-
if (!KINDS.includes(p.kind)) {
|
|
84
|
-
throw new Error(`kind must be one of: ${KINDS.join(", ")}`);
|
|
85
|
-
}
|
|
86
|
-
filtered = filtered.filter(r => r.kind === p.kind);
|
|
87
|
-
}
|
|
88
|
-
if (p.loadedOnly) filtered = filtered.filter(r => r.loaded);
|
|
89
|
-
return {
|
|
90
|
-
...rest,
|
|
91
|
-
total: filtered.length,
|
|
92
|
-
records: filtered.slice(0, limit).map(toWire)
|
|
93
|
-
};
|
|
94
|
-
},
|
|
95
|
-
/** Full detail for one asset. Params: { id }. */
|
|
96
|
-
getDetail: params => {
|
|
97
|
-
const record = requireRecord(params);
|
|
98
|
-
return {
|
|
99
|
-
...toWire(record),
|
|
100
|
-
origin: record.origin,
|
|
101
|
-
fileSystemLocation: record.fileSystemLocation ?? null,
|
|
102
|
-
files: record.files ?? null,
|
|
103
|
-
sizesByScale: record.sizesByScale ?? null,
|
|
104
|
-
estDecodedBytes: estDecodedBytes(record) || null
|
|
105
|
-
};
|
|
106
|
-
},
|
|
107
|
-
/** Re-walk the registry, merge the Metro graph, refresh fonts, measure sizes. */
|
|
108
|
-
rescan: async () => {
|
|
109
|
-
const status = await rescan();
|
|
110
|
-
return {
|
|
111
|
-
ok: true,
|
|
112
|
-
scanStatus: status
|
|
113
|
-
};
|
|
114
|
-
},
|
|
115
|
-
/** (Re-)measure byte sizes from the dev server. */
|
|
116
|
-
measureSizes: async () => {
|
|
117
|
-
const result = await measureSizes();
|
|
118
|
-
return {
|
|
119
|
-
ok: result.measured > 0,
|
|
120
|
-
...result
|
|
121
|
-
};
|
|
122
|
-
},
|
|
123
|
-
/** Persist the current inventory as the comparison baseline. */
|
|
124
|
-
saveBaseline: async () => {
|
|
125
|
-
const baseline = await saveBaseline();
|
|
126
|
-
return {
|
|
127
|
-
ok: true,
|
|
128
|
-
savedAt: baseline.savedAt,
|
|
129
|
-
assets: Object.keys(baseline.entries).length
|
|
130
|
-
};
|
|
131
|
-
},
|
|
132
|
-
/** Drop the saved baseline. */
|
|
133
|
-
clearBaseline: async () => {
|
|
134
|
-
await clearBaseline();
|
|
135
|
-
return {
|
|
136
|
-
ok: true,
|
|
137
|
-
message: "Baseline cleared"
|
|
138
|
-
};
|
|
139
|
-
},
|
|
140
|
-
/** Change vs the saved baseline (null when no baseline exists). */
|
|
141
|
-
getDiff: () => getDiff(),
|
|
142
|
-
/** Wipe the store (next rescan rebuilds it). */
|
|
143
|
-
clearRecords: () => {
|
|
144
|
-
clearRecords();
|
|
145
|
-
return {
|
|
146
|
-
ok: true,
|
|
147
|
-
message: "Inventory cleared — rescan to rebuild"
|
|
148
|
-
};
|
|
149
|
-
},
|
|
150
|
-
getScanStatus: () => getScanStatus()
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
//# sourceMappingURL=assetsSyncAdapter.js.map
|
|
1
|
+
"use strict";import{getScanStatus as o,rescan as f}from"../capture/install";import{measureSizes as y}from"../capture/measure";import{clearRecords as h,computeStats as g,estDecodedBytes as i,getRecord as S,getSnapshot as p,subscribe as k}from"../store/assetStore";import{clearBaseline as w,getDiff as l,saveBaseline as B}from"../store/baseline";import{computeInsights as z}from"../store/insights";const u=["image","font","video","audio","data","other"];function a(e){return{id:e.id,key:e.key,registryId:e.registryId??null,name:e.name,type:e.type,kind:e.kind,hash:e.hash??null,location:e.httpServerLocation??null,width:e.width??null,height:e.height??null,scales:e.scales,uri:e.resolvedUri??null,loaded:e.loaded,sizeBytes:e.sizeSource==="measured"?e.sizeBytes??null:i(e)||null,sizeSource:e.sizeSource??null}}function v(e){const t=e?.id;if(typeof t!=="number")throw new Error("Missing numeric `id` param");const n=S(t);if(!n){throw new Error(`Asset record ${t} not found \u2014 re-run the list action for current ids.`)}return n}function b(e,t){return(t.sizeBytes??i(t))-(e.sizeBytes??i(e))}function c(){const e=p();return{stats:g(e),scanStatus:o(),insights:z(e),diff:l(),records:[...e].sort(b)}}export const assetsSyncAdapter={version:1,getSnapshot:()=>{const{records:e,...t}=c();return{...t,records:e.slice(0,200).map(a)}},subscribe:e=>k(e),actions:{list:e=>{const t=e??{};const n=Math.min(Math.max(t.limit??50,1),500);const{records:d,...m}=c();let s=d;if(t.kind){if(!u.includes(t.kind)){throw new Error(`kind must be one of: ${u.join(", ")}`)}s=s.filter(r=>r.kind===t.kind)}if(t.loadedOnly)s=s.filter(r=>r.loaded);return{...m,total:s.length,records:s.slice(0,n).map(a)}},getDetail:e=>{const t=v(e);return{...a(t),origin:t.origin,fileSystemLocation:t.fileSystemLocation??null,files:t.files??null,sizesByScale:t.sizesByScale??null,estDecodedBytes:i(t)||null}},rescan:async()=>{const e=await f();return{ok:true,scanStatus:e}},measureSizes:async()=>{const e=await y();return{ok:e.measured>0,...e}},saveBaseline:async()=>{const e=await B();return{ok:true,savedAt:e.savedAt,assets:Object.keys(e.entries).length}},clearBaseline:async()=>{await w();return{ok:true,message:"Baseline cleared"}},getDiff:()=>l(),clearRecords:()=>{h();return{ok:true,message:"Inventory cleared \u2014 rescan to rebuild"}},getScanStatus:()=>o()}};
|
package/lib/module/types.js
CHANGED
|
@@ -1,46 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Which scale variant this device would actually use.
|
|
5
|
-
*
|
|
6
|
-
* Shared by the decoded-memory estimate and the scale-gap insight so the two
|
|
7
|
-
* can't disagree about the same asset — they were written apart and did.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { assetsState } from "../state";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* PixelRatio, or the DEVICE's when this store is a dashboard mirror.
|
|
14
|
-
*
|
|
15
|
-
* A desktop dashboard renders another machine's inventory, and its own
|
|
16
|
-
* PixelRatio has nothing to do with which variant the phone will pick.
|
|
17
|
-
*/
|
|
18
|
-
export function effectivePixelRatio() {
|
|
19
|
-
const mirror = assetsState().mirrorPixelRatio;
|
|
20
|
-
if (mirror != null) return mirror;
|
|
21
|
-
try {
|
|
22
|
-
return require("react-native").PixelRatio.get();
|
|
23
|
-
} catch {
|
|
24
|
-
return 2;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* The variant React Native would resolve, by AssetSourceResolver's rule: the
|
|
30
|
-
* smallest scale that covers the device, or the largest available when none
|
|
31
|
-
* does.
|
|
32
|
-
*
|
|
33
|
-
* Not simply "the biggest one shipped". An asset with @1x…@4x variants on a
|
|
34
|
-
* @3x phone decodes at 3, and calling it 4 overstates its memory by 78% —
|
|
35
|
-
* on exactly the never-loaded assets this tool exists to point at, since a
|
|
36
|
-
* loaded one carries the scale RN already picked.
|
|
37
|
-
*/
|
|
38
|
-
export function pickScale(scales, deviceScale) {
|
|
39
|
-
if (!scales.length) return 1;
|
|
40
|
-
const ordered = [...scales].sort((a, b) => a - b);
|
|
41
|
-
for (const scale of ordered) {
|
|
42
|
-
if (scale >= deviceScale) return scale;
|
|
43
|
-
}
|
|
44
|
-
return ordered[ordered.length - 1];
|
|
45
|
-
}
|
|
46
|
-
//# sourceMappingURL=deviceScale.js.map
|
|
1
|
+
"use strict";import{assetsState as n}from"../state";export function effectivePixelRatio(){const r=n().mirrorPixelRatio;if(r!=null)return r;try{return require("react-native").PixelRatio.get()}catch{return 2}}export function pickScale(r,i){if(!r.length)return 1;const e=[...r].sort((t,o)=>t-o);for(const t of e){if(t>=i)return t}return e[e.length-1]}
|
|
@@ -1,99 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Automation handles for the Assets tool.
|
|
5
|
-
*
|
|
6
|
-
* Every handle the tool renders is built here rather than inlined at the call
|
|
7
|
-
* site, for the same reason the highlight tool does it: the ids for rows,
|
|
8
|
-
* badges, filter chips and detail fields are DERIVED from data, and a suite
|
|
9
|
-
* that reimplements the derivation is a suite that can drift from the tool
|
|
10
|
-
* without either side failing. `detox-e2e/assets/fixture.js` mirrors these
|
|
11
|
-
* rules in CommonJS (the tests can't import TypeScript) and asserts the two
|
|
12
|
-
* still agree before any flow trusts a handle.
|
|
13
|
-
*
|
|
14
|
-
* Naming: `buoy-assets-<area>[-<detail>]`, one prefix for the whole tool.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Lowercase, hyphen-joined, never empty.
|
|
19
|
-
*
|
|
20
|
-
* Shared by rows, badges, filter chips and detail fields so one asset's row
|
|
21
|
-
* handle and its detail handles are slugged the same way — `back-icon.png`
|
|
22
|
-
* becomes `back-icon-png` everywhere it appears.
|
|
23
|
-
*/
|
|
24
|
-
export function assetSlug(value) {
|
|
25
|
-
return String(value).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "unknown";
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* One inventory row, addressed by filename rather than by store id.
|
|
30
|
-
*
|
|
31
|
-
* The store's numeric ids are assigned in first-sighting order and are NOT
|
|
32
|
-
* stable: clearing the inventory and rescanning renumbers everything, and the
|
|
33
|
-
* order the registry walk and the Metro graph merge in depends on what the app
|
|
34
|
-
* has evaluated so far. A handle built from those makes a test that passes
|
|
35
|
-
* only on the run that recorded it.
|
|
36
|
-
*
|
|
37
|
-
* The tradeoff is that two assets with the same filename in different
|
|
38
|
-
* directories collide. That's a real ambiguity rather than a hidden one —
|
|
39
|
-
* Detox reports it as "multiple elements matched", which names the problem —
|
|
40
|
-
* and it's the same tradeoff the row itself makes, since the row shows the
|
|
41
|
-
* filename and not the path.
|
|
42
|
-
*/
|
|
43
|
-
export function assetRowTestID(name, type) {
|
|
44
|
-
return `buoy-assets-row-${assetSlug(`${name}.${type}`)}`;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** A finding badge inside a row: `…-badge-low-res`, `…-badge-dup`. */
|
|
48
|
-
export function assetBadgeTestID(name, type, label) {
|
|
49
|
-
return `${assetRowTestID(name, type)}-badge-${assetSlug(label)}`;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** A kind/status filter chip: `buoy-assets-filter-font`, `…-filter-unused`. */
|
|
53
|
-
export function assetFilterTestID(filter) {
|
|
54
|
-
return `buoy-assets-filter-${assetSlug(filter)}`;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** One label/value pair in the detail view: `buoy-assets-detail-row-hash`. */
|
|
58
|
-
export function assetDetailRowTestID(label) {
|
|
59
|
-
return `buoy-assets-detail-row-${assetSlug(label)}`;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** A loaded font-family chip: `buoy-assets-font-ionicons`. */
|
|
63
|
-
export function assetFontChipTestID(family) {
|
|
64
|
-
return `buoy-assets-font-${assetSlug(family)}`;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** Fixed handles — everything that isn't derived from a record. */
|
|
68
|
-
export const ASSETS_TEST_IDS = {
|
|
69
|
-
// header
|
|
70
|
-
title: "buoy-assets-title",
|
|
71
|
-
copy: "buoy-assets-copy",
|
|
72
|
-
clear: "buoy-assets-clear",
|
|
73
|
-
// list chrome
|
|
74
|
-
list: "buoy-assets-list",
|
|
75
|
-
search: "buoy-assets-search",
|
|
76
|
-
sort: "buoy-assets-sort",
|
|
77
|
-
filterBar: "buoy-assets-filter-bar",
|
|
78
|
-
notice: "buoy-assets-notice",
|
|
79
|
-
noticeText: "buoy-assets-notice-text",
|
|
80
|
-
discoverySpinner: "buoy-assets-discovery-spinner",
|
|
81
|
-
retry: "buoy-assets-retry",
|
|
82
|
-
fonts: "buoy-assets-fonts",
|
|
83
|
-
fontsTitle: "buoy-assets-fonts-title",
|
|
84
|
-
empty: "buoy-assets-empty",
|
|
85
|
-
emptyTitle: "buoy-assets-empty-title",
|
|
86
|
-
emptyText: "buoy-assets-empty-text",
|
|
87
|
-
// detail
|
|
88
|
-
detail: "buoy-assets-detail",
|
|
89
|
-
detailScroll: "buoy-assets-detail-scroll",
|
|
90
|
-
detailPreview: "buoy-assets-detail-preview",
|
|
91
|
-
detailFontPreview: "buoy-assets-detail-font-preview",
|
|
92
|
-
detailFindings: "buoy-assets-detail-findings"
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
/** One finding sentence in the detail view, by position. */
|
|
96
|
-
export function assetFindingTestID(index) {
|
|
97
|
-
return `buoy-assets-detail-finding-${index}`;
|
|
98
|
-
}
|
|
99
|
-
//# sourceMappingURL=testIDs.js.map
|
|
1
|
+
"use strict";export function assetSlug(t){return String(t).toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"unknown"}export function assetRowTestID(t,e){return`buoy-assets-row-${assetSlug(`${t}.${e}`)}`}export function assetBadgeTestID(t,e,s){return`${assetRowTestID(t,e)}-badge-${assetSlug(s)}`}export function assetFilterTestID(t){return`buoy-assets-filter-${assetSlug(t)}`}export function assetDetailRowTestID(t){return`buoy-assets-detail-row-${assetSlug(t)}`}export function assetFontChipTestID(t){return`buoy-assets-font-${assetSlug(t)}`}export const ASSETS_TEST_IDS={title:"buoy-assets-title",copy:"buoy-assets-copy",clear:"buoy-assets-clear",list:"buoy-assets-list",search:"buoy-assets-search",sort:"buoy-assets-sort",filterBar:"buoy-assets-filter-bar",notice:"buoy-assets-notice",noticeText:"buoy-assets-notice-text",discoverySpinner:"buoy-assets-discovery-spinner",retry:"buoy-assets-retry",fonts:"buoy-assets-fonts",fontsTitle:"buoy-assets-fonts-title",empty:"buoy-assets-empty",emptyTitle:"buoy-assets-empty-title",emptyText:"buoy-assets-empty-text",detail:"buoy-assets-detail",detailScroll:"buoy-assets-detail-scroll",detailPreview:"buoy-assets-detail-preview",detailFontPreview:"buoy-assets-detail-font-preview",detailFindings:"buoy-assets-detail-findings"};export function assetFindingTestID(t){return`buoy-assets-detail-finding-${t}`}
|