@invarn/cibuild 2.0.9 → 2.1.0
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/cli.cjs +6 -6
- package/dist/src/yaml/steps/render-post-processor.d.ts.map +1 -1
- package/dist/src/yaml/steps/render-post-processor.js +19 -6
- package/dist/src/yaml/steps/render-post-processor.test.js +46 -2
- package/dist/src/yaml/steps/ui-fidelity-preview-android.d.ts.map +1 -1
- package/dist/src/yaml/steps/ui-fidelity-preview-android.js +40 -7
- package/dist/src/yaml/steps/ui-fidelity-preview-android.test.js +53 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-post-processor.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/render-post-processor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH;;;;;;;;;;;;;;;;GAgBG;AACH;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,EAAE,
|
|
1
|
+
{"version":3,"file":"render-post-processor.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/render-post-processor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH;;;;;;;;;;;;;;;;GAgBG;AACH;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,EAAE,MA8JpB,CAAC;AAEpB,eAAO,MAAM,wBAAwB,EAAE,MAsFtC,CAAC;AAEF,iFAAiF;AACjF,MAAM,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE5C,iFAAiF;AACjF,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,YAAY,CAAC;IACvB,SAAS,EAAE,YAAY,CAAC;IACxB,MAAM,EAAE,YAAY,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAAC;IAC/C,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;CACjC;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,IAAI,sBAAsB,CAMlE"}
|
|
@@ -61,14 +61,25 @@ export const TRIM_HELPER_SWIFT_SOURCE = [
|
|
|
61
61
|
' return CGImageDestinationFinalize(dest)',
|
|
62
62
|
'}',
|
|
63
63
|
'',
|
|
64
|
-
'//
|
|
65
|
-
'
|
|
64
|
+
'// Draw `image` centered on a transparent reference-sized canvas, scaled to fit',
|
|
65
|
+
'// while PRESERVING its aspect ratio. Unlike `resize` (which stretches to exact',
|
|
66
|
+
'// dimensions and distorts proportions when the aspect ratios differ), this',
|
|
67
|
+
'// keeps the rendered shape honest: an aspect mismatch shows as transparent',
|
|
68
|
+
'// letterbox/pillarbox padding rather than a squished image, while the canvas',
|
|
69
|
+
'// stays exactly reference-sized so the pair still overlays 1:1.',
|
|
70
|
+
'func fitPad(_ image: CGImage, toWidth w: Int, height h: Int) -> CGImage? {',
|
|
66
71
|
' if w <= 0 || h <= 0 { return nil }',
|
|
67
72
|
' let bytesPerRow = w * 4',
|
|
68
73
|
' let space = CGColorSpaceCreateDeviceRGB()',
|
|
69
74
|
' guard let ctx = CGContext(data: nil, width: w, height: h, bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: space, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else { return nil }',
|
|
75
|
+
' ctx.clear(CGRect(x: 0, y: 0, width: w, height: h))',
|
|
70
76
|
' ctx.interpolationQuality = .high',
|
|
71
|
-
'
|
|
77
|
+
' let scale = min(Double(w) / Double(image.width), Double(h) / Double(image.height))',
|
|
78
|
+
' let drawW = Double(image.width) * scale',
|
|
79
|
+
' let drawH = Double(image.height) * scale',
|
|
80
|
+
' let originX = (Double(w) - drawW) / 2.0',
|
|
81
|
+
' let originY = (Double(h) - drawH) / 2.0',
|
|
82
|
+
' ctx.draw(image, in: CGRect(x: originX, y: originY, width: drawW, height: drawH))',
|
|
72
83
|
' return ctx.makeImage()',
|
|
73
84
|
'}',
|
|
74
85
|
'',
|
|
@@ -169,8 +180,10 @@ export const TRIM_HELPER_SWIFT_SOURCE = [
|
|
|
169
180
|
' exit(68)',
|
|
170
181
|
'}',
|
|
171
182
|
'',
|
|
172
|
-
'// Optional aligned output: the trimmed image
|
|
173
|
-
'//
|
|
183
|
+
'// Optional aligned output: the trimmed image fit onto a reference-sized',
|
|
184
|
+
'// canvas, preserving aspect ratio (transparent letterbox/pillarbox padding',
|
|
185
|
+
'// when aspect ratios differ). A reference-sized, undistorted overlay pair.',
|
|
186
|
+
'// Best-effort; dimensions are reported either way.',
|
|
174
187
|
'var referenceWidth = 0',
|
|
175
188
|
'var referenceHeight = 0',
|
|
176
189
|
'if arguments.count >= 5 {',
|
|
@@ -179,7 +192,7 @@ export const TRIM_HELPER_SWIFT_SOURCE = [
|
|
|
179
192
|
' if let reference = loadCGImage(referencePath) {',
|
|
180
193
|
' referenceWidth = reference.width',
|
|
181
194
|
' referenceHeight = reference.height',
|
|
182
|
-
' if let aligned =
|
|
195
|
+
' if let aligned = fitPad(cropped, toWidth: referenceWidth, height: referenceHeight) {',
|
|
183
196
|
' _ = writePNG(aligned, to: alignedPath)',
|
|
184
197
|
' }',
|
|
185
198
|
' }',
|
|
@@ -116,13 +116,39 @@ describe('trim helper pixel logic (real toolchain)', () => {
|
|
|
116
116
|
const BLUE = { r: 0, g: 0, b: 1, a: 1 };
|
|
117
117
|
const WHITE = { r: 1, g: 1, b: 1, a: 1 };
|
|
118
118
|
const PINK = { r: 1, g: 240 / 255, b: 238 / 255, a: 1 };
|
|
119
|
+
// Prints "<r> <g> <b> <a>" (0..255) for one pixel, so a test can tell padding
|
|
120
|
+
// (transparent) apart from content (opaque) in an aligned image.
|
|
121
|
+
const PROBE_SWIFT = [
|
|
122
|
+
'import Foundation',
|
|
123
|
+
'import ImageIO',
|
|
124
|
+
'import CoreGraphics',
|
|
125
|
+
'let src = CGImageSourceCreateWithURL(URL(fileURLWithPath: CommandLine.arguments[1]) as CFURL, nil)!',
|
|
126
|
+
'let img = CGImageSourceCreateImageAtIndex(src, 0, nil)!',
|
|
127
|
+
'let x = Int(CommandLine.arguments[2])!, y = Int(CommandLine.arguments[3])!',
|
|
128
|
+
'let w = img.width, h = img.height',
|
|
129
|
+
'var data = [UInt8](repeating: 0, count: w * h * 4)',
|
|
130
|
+
'let cs = CGColorSpaceCreateDeviceRGB()',
|
|
131
|
+
'let c = CGContext(data: &data, width: w, height: h, bitsPerComponent: 8, bytesPerRow: w * 4, space: cs, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)!',
|
|
132
|
+
'c.draw(img, in: CGRect(x: 0, y: 0, width: w, height: h))',
|
|
133
|
+
'let i = y * w * 4 + x * 4',
|
|
134
|
+
'print("\\(data[i]) \\(data[i+1]) \\(data[i+2]) \\(data[i+3])")',
|
|
135
|
+
].join('\n') + '\n';
|
|
119
136
|
function scene() {
|
|
120
137
|
const dir = mkdtempSync(join(tmpdir(), 'ui-fidelity-postproc-'));
|
|
121
138
|
dirs.push(dir);
|
|
122
139
|
writeFileSync(join(dir, 'Make.swift'), MAKE_FIXTURE_SWIFT);
|
|
123
140
|
writeFileSync(join(dir, 'Trim.swift'), TRIM_HELPER_SWIFT_SOURCE);
|
|
141
|
+
writeFileSync(join(dir, 'Probe.swift'), PROBE_SWIFT);
|
|
124
142
|
return dir;
|
|
125
143
|
}
|
|
144
|
+
// Alpha (0..255) of one pixel in a PNG.
|
|
145
|
+
function alphaAt(dir, filePath, x, y) {
|
|
146
|
+
const probe = spawnSync('swift', [join(dir, 'Probe.swift'), filePath, String(x), String(y)], {
|
|
147
|
+
encoding: 'utf-8',
|
|
148
|
+
});
|
|
149
|
+
expect(probe.status).toBe(0);
|
|
150
|
+
return Number(probe.stdout.trim().split(/\s+/)[3]);
|
|
151
|
+
}
|
|
126
152
|
function craft(dir, name, w, h, rects) {
|
|
127
153
|
const out = join(dir, name);
|
|
128
154
|
const make = spawnSync('swift', [join(dir, 'Make.swift'), out, JSON.stringify({ w, h, rects })], { encoding: 'utf-8' });
|
|
@@ -178,8 +204,8 @@ describe('trim helper pixel logic (real toolchain)', () => {
|
|
|
178
204
|
const out = join(dir, 'out.png');
|
|
179
205
|
const aligned = join(dir, 'aligned.png');
|
|
180
206
|
const result = trim(dir, input, out, aligned, reference);
|
|
181
|
-
// The trimmed render is the content; the aligned copy is
|
|
182
|
-
// reference
|
|
207
|
+
// The trimmed render is the content; the aligned copy is fit onto a
|
|
208
|
+
// reference-sized canvas (preserving aspect) for a 1:1 overlay.
|
|
183
209
|
expect(pngSize(out)).toEqual({ width: 60, height: 40 });
|
|
184
210
|
expect(pngSize(aligned)).toEqual({ width: 100, height: 50 });
|
|
185
211
|
// The TRIMDIMS line reports rendered (trimmed) / reference / pre-trim
|
|
@@ -190,5 +216,23 @@ describe('trim helper pixel logic (real toolchain)', () => {
|
|
|
190
216
|
source: [200, 200],
|
|
191
217
|
});
|
|
192
218
|
}, 600_000);
|
|
219
|
+
realSwiftTest('fits a wide render into a square reference without distortion (transparent letterbox)', () => {
|
|
220
|
+
const dir = scene();
|
|
221
|
+
// Content fills its canvas: a 2:1 render (80x40), all opaque red.
|
|
222
|
+
const input = craft(dir, 'in.png', 80, 40, [{ x: 0, y: 0, w: 80, h: 40, ...RED }]);
|
|
223
|
+
// Square 1:1 reference (50x50) — a deliberately different aspect ratio.
|
|
224
|
+
const reference = craft(dir, 'ref.png', 50, 50, [{ x: 0, y: 0, w: 50, h: 50, ...BLUE }]);
|
|
225
|
+
const out = join(dir, 'out.png');
|
|
226
|
+
const aligned = join(dir, 'aligned.png');
|
|
227
|
+
trim(dir, input, out, aligned, reference);
|
|
228
|
+
// Aligned stays exactly reference-sized (overlay pair holds).
|
|
229
|
+
expect(pngSize(aligned)).toEqual({ width: 50, height: 50 });
|
|
230
|
+
// Aspect preserved: the 2:1 content fits by width (50x25) and is centered,
|
|
231
|
+
// so both letterbox bands are transparent while the center is opaque
|
|
232
|
+
// content. A stretch-to-fit would make every one of these opaque.
|
|
233
|
+
expect(alphaAt(dir, aligned, 25, 3)).toBe(0); // top band
|
|
234
|
+
expect(alphaAt(dir, aligned, 25, 46)).toBe(0); // bottom band
|
|
235
|
+
expect(alphaAt(dir, aligned, 25, 25)).toBe(255); // content (center)
|
|
236
|
+
}, 600_000);
|
|
193
237
|
});
|
|
194
238
|
//# sourceMappingURL=render-post-processor.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui-fidelity-preview-android.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/ui-fidelity-preview-android.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAsBH,OAAO,KAAK,EAEV,uBAAuB,EACvB,uBAAuB,EAExB,MAAM,0BAA0B,CAAC;AAElC,MAAM,WAAW,+BAA+B;IAC9C;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,OAAO,EAAE,uBAAuB,EAAE,CAAC;IACnC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAkBD;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"ui-fidelity-preview-android.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/ui-fidelity-preview-android.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAsBH,OAAO,KAAK,EAEV,uBAAuB,EACvB,uBAAuB,EAExB,MAAM,0BAA0B,CAAC;AAElC,MAAM,WAAW,+BAA+B;IAC9C;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,OAAO,EAAE,uBAAuB,EAAE,CAAC;IACnC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAkBD;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,8BAA8B,CAAC;AAqErE;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,+BAA+B,GACvC,uBAAuB,CA6KzB"}
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import { spawnSync } from 'node:child_process';
|
|
22
22
|
import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, statSync, writeFileSync, } from 'node:fs';
|
|
23
|
-
import { tmpdir } from 'node:os';
|
|
23
|
+
import { homedir, tmpdir } from 'node:os';
|
|
24
24
|
import { basename, join, resolve } from 'node:path';
|
|
25
25
|
import { parseScale, DEFAULT_SCALE } from './ui-fidelity-render.js';
|
|
26
26
|
import { DEFAULT_GRADLE_TASK, PACKAGE_ARCHIVE_BASENAME, generateAndroidRenderScript, isValidGradleTask, } from './ui-fidelity-render-android.js';
|
|
@@ -51,19 +51,44 @@ function commandExists(command, pathPrepend) {
|
|
|
51
51
|
: spawnSync(`command -v ${command}`, { encoding: 'utf-8', env, shell: true });
|
|
52
52
|
return probe.status === 0 && (probe.stdout || '').trim() !== '';
|
|
53
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Resolve the Android SDK directory: prefer an explicit `ANDROID_HOME` /
|
|
56
|
+
* `ANDROID_SDK_ROOT` env var (when it points at a real directory), otherwise
|
|
57
|
+
* fall back to the platform's default install location. This mirrors the
|
|
58
|
+
* auto-detect the Android build step already does, so a dev machine with a
|
|
59
|
+
* standard Android Studio install previews locally without the caller (or the
|
|
60
|
+
* MCP server's launch environment) having to export `ANDROID_HOME`. Returns the
|
|
61
|
+
* resolved path, or null when no SDK can be found.
|
|
62
|
+
*/
|
|
63
|
+
function resolveAndroidSdk() {
|
|
64
|
+
const fromEnv = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT || '';
|
|
65
|
+
if (fromEnv && existsSync(fromEnv))
|
|
66
|
+
return fromEnv;
|
|
67
|
+
const home = process.env.HOME || homedir();
|
|
68
|
+
const candidates = [
|
|
69
|
+
join(home, 'Library', 'Android', 'sdk'), // macOS
|
|
70
|
+
join(home, 'Android', 'Sdk'), // Linux
|
|
71
|
+
'/usr/local/android-sdk',
|
|
72
|
+
];
|
|
73
|
+
for (const candidate of candidates) {
|
|
74
|
+
if (existsSync(candidate))
|
|
75
|
+
return candidate;
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
54
79
|
/**
|
|
55
80
|
* Report which env-level Android prerequisites are missing: the JVM (`java`) and
|
|
56
|
-
* the SDK (
|
|
81
|
+
* the SDK (a resolved [resolveAndroidSdk] path). Gradle itself ships inside the
|
|
57
82
|
* render project (gradlew), so it is not an env-level prerequisite. Returns an
|
|
58
83
|
* empty array when the toolchain is present.
|
|
59
84
|
*/
|
|
60
|
-
function missingToolchain(pathPrepend) {
|
|
85
|
+
function missingToolchain(sdk, pathPrepend) {
|
|
61
86
|
const missing = [];
|
|
62
87
|
if (!commandExists('java', pathPrepend))
|
|
63
88
|
missing.push('a JDK (java not on PATH)');
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
89
|
+
if (!sdk) {
|
|
90
|
+
missing.push('the Android SDK (ANDROID_HOME not set and no SDK at the default location)');
|
|
91
|
+
}
|
|
67
92
|
return missing;
|
|
68
93
|
}
|
|
69
94
|
/** Resolve an artifact-relative path to absolute, or null. */
|
|
@@ -122,7 +147,8 @@ export function renderUiFidelityPreviewAndroid(options) {
|
|
|
122
147
|
// A render is a heavy Gradle/Robolectric build; if the dev machine has no
|
|
123
148
|
// local Android toolchain at all, fall back to a remote run with a clear
|
|
124
149
|
// message instead of letting the build fail confusingly.
|
|
125
|
-
const
|
|
150
|
+
const androidSdk = resolveAndroidSdk();
|
|
151
|
+
const missing = missingToolchain(androidSdk, options.toolchainPath);
|
|
126
152
|
if (missing.length > 0) {
|
|
127
153
|
return {
|
|
128
154
|
packageSource: 'inputs',
|
|
@@ -187,6 +213,13 @@ export function renderUiFidelityPreviewAndroid(options) {
|
|
|
187
213
|
if (options.toolchainPath) {
|
|
188
214
|
env.PATH = options.toolchainPath + ':' + (process.env.PATH ?? '');
|
|
189
215
|
}
|
|
216
|
+
// Export the resolved SDK so gradlew sees it even when ANDROID_HOME was
|
|
217
|
+
// auto-detected (env unset) rather than already present in process.env.
|
|
218
|
+
if (androidSdk) {
|
|
219
|
+
env.ANDROID_HOME = androidSdk;
|
|
220
|
+
if (!process.env.ANDROID_SDK_ROOT)
|
|
221
|
+
env.ANDROID_SDK_ROOT = androidSdk;
|
|
222
|
+
}
|
|
190
223
|
const run = spawnSync('node', ['-e', script], {
|
|
191
224
|
cwd: workDir,
|
|
192
225
|
env,
|
|
@@ -189,8 +189,13 @@ describe('renderUiFidelityPreviewAndroid — missing local toolchain', () => {
|
|
|
189
189
|
test('reports a clear toolchain-missing build error instead of crashing', () => {
|
|
190
190
|
const savedHome = process.env.ANDROID_HOME;
|
|
191
191
|
const savedRoot = process.env.ANDROID_SDK_ROOT;
|
|
192
|
+
const savedUserHome = process.env.HOME;
|
|
192
193
|
delete process.env.ANDROID_HOME;
|
|
193
194
|
delete process.env.ANDROID_SDK_ROOT;
|
|
195
|
+
// Point HOME at an empty dir so the default-location auto-detect
|
|
196
|
+
// (~/Library/Android/sdk, ~/Android/Sdk) finds nothing — otherwise a dev
|
|
197
|
+
// machine with the SDK installed would resolve one and this guard wouldn't fire.
|
|
198
|
+
process.env.HOME = tempDir('ui-fidelity-android-emptyhome-');
|
|
194
199
|
try {
|
|
195
200
|
const result = renderUiFidelityPreviewAndroid({
|
|
196
201
|
packagePath: gradleProject(),
|
|
@@ -211,6 +216,52 @@ describe('renderUiFidelityPreviewAndroid — missing local toolchain', () => {
|
|
|
211
216
|
delete process.env.ANDROID_SDK_ROOT;
|
|
212
217
|
else
|
|
213
218
|
process.env.ANDROID_SDK_ROOT = savedRoot;
|
|
219
|
+
if (savedUserHome === undefined)
|
|
220
|
+
delete process.env.HOME;
|
|
221
|
+
else
|
|
222
|
+
process.env.HOME = savedUserHome;
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
test('auto-detects the SDK at the default install location when ANDROID_HOME is unset', () => {
|
|
226
|
+
const savedHome = process.env.ANDROID_HOME;
|
|
227
|
+
const savedRoot = process.env.ANDROID_SDK_ROOT;
|
|
228
|
+
const savedUserHome = process.env.HOME;
|
|
229
|
+
const savedScreens = process.env.FAKE_ROBORAZZI_SCREENS;
|
|
230
|
+
delete process.env.ANDROID_HOME;
|
|
231
|
+
delete process.env.ANDROID_SDK_ROOT;
|
|
232
|
+
// A clean HOME with an SDK at the macOS default location, but no ANDROID_HOME.
|
|
233
|
+
const fakeHome = tempDir('ui-fidelity-android-home-');
|
|
234
|
+
mkdirSync(join(fakeHome, 'Library', 'Android', 'sdk'), { recursive: true });
|
|
235
|
+
process.env.HOME = fakeHome;
|
|
236
|
+
process.env.FAKE_ROBORAZZI_SCREENS = 'XProof';
|
|
237
|
+
try {
|
|
238
|
+
const result = renderUiFidelityPreviewAndroid({
|
|
239
|
+
packagePath: gradleProject(),
|
|
240
|
+
screens: [{ screen: 'XProof', reference: writeReference() }],
|
|
241
|
+
toolchainPath: fakeToolchain(),
|
|
242
|
+
});
|
|
243
|
+
// Auto-detected the SDK and rendered, rather than bailing with TOOLCHAIN_MISSING.
|
|
244
|
+
expect(result.buildError).toBeNull();
|
|
245
|
+
expect(result.ok).toBe(true);
|
|
246
|
+
expect(result.screens[0]?.status).toBe('rendered');
|
|
247
|
+
}
|
|
248
|
+
finally {
|
|
249
|
+
if (savedHome === undefined)
|
|
250
|
+
delete process.env.ANDROID_HOME;
|
|
251
|
+
else
|
|
252
|
+
process.env.ANDROID_HOME = savedHome;
|
|
253
|
+
if (savedRoot === undefined)
|
|
254
|
+
delete process.env.ANDROID_SDK_ROOT;
|
|
255
|
+
else
|
|
256
|
+
process.env.ANDROID_SDK_ROOT = savedRoot;
|
|
257
|
+
if (savedUserHome === undefined)
|
|
258
|
+
delete process.env.HOME;
|
|
259
|
+
else
|
|
260
|
+
process.env.HOME = savedUserHome;
|
|
261
|
+
if (savedScreens === undefined)
|
|
262
|
+
delete process.env.FAKE_ROBORAZZI_SCREENS;
|
|
263
|
+
else
|
|
264
|
+
process.env.FAKE_ROBORAZZI_SCREENS = savedScreens;
|
|
214
265
|
}
|
|
215
266
|
});
|
|
216
267
|
});
|
|
@@ -218,7 +269,8 @@ describe('renderUiFidelityPreviewAndroid — missing local toolchain', () => {
|
|
|
218
269
|
// toolchain and asserts a non-blank paired render at the expected size. Requires
|
|
219
270
|
// the Android toolchain (JDK + SDK) and warm Roborazzi caches; gated behind
|
|
220
271
|
// CIBUILD_UI_FIDELITY_REAL_ANDROID=1 (the Android analog of the iOS real-Swift
|
|
221
|
-
// gate).
|
|
272
|
+
// gate). ANDROID_HOME need not be exported — a standard SDK install at the
|
|
273
|
+
// default location (~/Library/Android/sdk etc.) is auto-detected.
|
|
222
274
|
describe('renderUiFidelityPreviewAndroid — real toolchain', () => {
|
|
223
275
|
const realAndroidTest = process.env.CIBUILD_UI_FIDELITY_REAL_ANDROID === '1' ? test : test.skip;
|
|
224
276
|
// A real, decodable 1x1 PNG so the trim helper's reference load/align succeeds.
|