Skip to content

Commit afd27bc

Browse files
committed
Loop back through animation demo forms before waiting for host
1 parent 01ad037 commit afd27bc

File tree

2 files changed

+291
-16
lines changed

2 files changed

+291
-16
lines changed

.github/workflows/developer-guide-docs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ jobs:
5151
mkdir -p "$HOME/.codenameone"
5252
touch "$HOME/.codenameone/guibuilder.jar"
5353
cp maven/CodeNameOneBuildClient.jar "$HOME/.codenameone/CodeNameOneBuildClient.jar"
54-
xvfb-run -a mvn -B -ntp -Dgenerate-gui-sources-done=true -pl common -am -f docs/demos/pom.xml install
54+
xvfb-run -a mvn -B -ntp -Dgenerate-gui-sources-done=true -am -f docs/demos/pom.xml install
5555
5656
- name: Install ImageMagick for screenshot comparison
5757
if: github.event_name != 'pull_request' || steps.changes.outputs.demos == 'true' || steps.changes.outputs.workflow == 'true'

docs/demos/common/src/test/java/com/codenameone/developerguide/animations/AnimationDemosScreenshotTest.java

Lines changed: 290 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,25 @@
44
import com.codename1.io.Util;
55
import com.codename1.testing.AbstractTest;
66
import com.codename1.testing.TestUtils;
7+
import com.codename1.ui.AnimationManager;
8+
import com.codename1.ui.Button;
9+
import com.codename1.ui.Component;
10+
import com.codename1.ui.Container;
711
import com.codename1.ui.Display;
812
import com.codename1.ui.Form;
913
import com.codename1.ui.Image;
14+
import com.codename1.ui.animations.Motion;
1015
import com.codename1.ui.util.ImageIO;
1116
import com.codenameone.developerguide.Demo;
17+
import com.codenameone.developerguide.DemoBrowserForm;
1218
import com.codenameone.developerguide.DemoRegistry;
1319

1420
import java.io.IOException;
1521
import java.io.OutputStream;
22+
import java.util.ArrayList;
1623
import java.util.HashMap;
1724
import java.util.HashSet;
25+
import java.util.List;
1826
import java.util.Map;
1927
import java.util.Set;
2028

@@ -23,9 +31,14 @@
2331
* that external tooling can compare them against the developer guide imagery.
2432
*/
2533
public class AnimationDemosScreenshotTest extends AbstractTest {
26-
private static final String HOST_TITLE = "Demo Test Host";
34+
private static final String HOST_TITLE = "Developer Guide Demos";
2735
private static final long FORM_TIMEOUT_MS = 10000L;
2836
private static final String STORAGE_PREFIX = "developer-guide.animations.";
37+
private static final int FRAMES_PER_ANIMATION = 6;
38+
private static final long ANIMATION_CAPTURE_TIMEOUT_MS = 2000L;
39+
private static final long ANIMATION_SETTLE_TIMEOUT_MS = 1500L;
40+
private static final int ANIMATION_FRAME_DELAY_MS = 180;
41+
private static final String FRAME_MANIFEST_SUFFIX = "-frames.manifest";
2942

3043
private static final Map<String, String> SCREENSHOT_NAME_OVERRIDES = createScreenshotNameOverrides();
3144
private static final Set<String> OVERRIDE_FILE_NAMES = new HashSet<>(SCREENSHOT_NAME_OVERRIDES.values());
@@ -36,24 +49,39 @@ public class AnimationDemosScreenshotTest extends AbstractTest {
3649
public boolean runTest() throws Exception {
3750
clearPreviousScreenshots();
3851

39-
Form host = new Form(HOST_TITLE);
40-
host.show();
41-
TestUtils.waitForFormTitle(HOST_TITLE, FORM_TIMEOUT_MS);
52+
boolean previousSlowMotion = Motion.isSlowMotion();
53+
Motion.setSlowMotion(true);
54+
try {
55+
Form host = new DemoBrowserForm();
56+
host.show();
57+
TestUtils.waitForFormTitle(HOST_TITLE, FORM_TIMEOUT_MS);
4258

43-
for (Demo demo : DemoRegistry.getDemos()) {
44-
Form previous = Display.getInstance().getCurrent();
45-
demo.show(host);
46-
Form demoForm = waitForFormChange(previous);
47-
waitForFormReady(demoForm);
59+
for (Demo demo : DemoRegistry.getDemos()) {
60+
Form previous = Display.getInstance().getCurrent();
61+
demo.show(host);
62+
Form demoForm = waitForFormChange(previous);
63+
waitForFormReady(demoForm);
4864

49-
Image screenshot = capture(demoForm);
50-
saveScreenshot(storageKeyFor(demo.getTitle()), screenshot);
65+
waitForAnimationsToFinish(demoForm);
5166

52-
host.show();
53-
waitForHost(host);
54-
}
67+
triggerAnimationIfNeeded(demo, demoForm);
68+
Form activeForm = ensureCurrentFormReady(demoForm);
5569

56-
return true;
70+
if (waitForAnimationStart(activeForm, ANIMATION_CAPTURE_TIMEOUT_MS)) {
71+
captureAnimationFrames(demo, activeForm);
72+
finalizeAnimations(activeForm);
73+
} else {
74+
Image screenshot = capture(activeForm);
75+
saveScreenshot(storageKeyFor(demo.getTitle()), screenshot);
76+
}
77+
78+
returnToHost(activeForm, host);
79+
}
80+
81+
return true;
82+
} finally {
83+
Motion.setSlowMotion(previousSlowMotion);
84+
}
5785
}
5886

5987
private void clearPreviousScreenshots() {
@@ -125,6 +153,7 @@ private void waitForFormReady(Form form) {
125153
private void waitForHost(Form host) {
126154
long deadline = System.currentTimeMillis() + FORM_TIMEOUT_MS;
127155
while (Display.getInstance().getCurrent() != host) {
156+
Display.getInstance().animate();
128157
TestUtils.waitFor(50);
129158
if (System.currentTimeMillis() > deadline) {
130159
fail("Timed out waiting to return to host form.");
@@ -134,6 +163,207 @@ private void waitForHost(Form host) {
134163
TestUtils.waitFor(200);
135164
}
136165

166+
private void returnToHost(Form demoForm, Form host) {
167+
if (host == null) {
168+
return;
169+
}
170+
171+
long deadline = System.currentTimeMillis() + FORM_TIMEOUT_MS;
172+
173+
if (demoForm != null && demoForm != currentForm()) {
174+
demoForm.showBack();
175+
}
176+
177+
while (System.currentTimeMillis() <= deadline) {
178+
Form active = currentForm();
179+
if (active == host) {
180+
break;
181+
}
182+
183+
if (active == null) {
184+
host.show();
185+
} else {
186+
active.showBack();
187+
}
188+
189+
TestUtils.waitFor(150);
190+
}
191+
192+
if (currentForm() != host) {
193+
host.show();
194+
}
195+
196+
waitForHost(host);
197+
}
198+
199+
private Form currentForm() {
200+
Component current = Display.getInstance().getCurrent();
201+
return (current instanceof Form) ? (Form) current : null;
202+
}
203+
204+
private void triggerAnimationIfNeeded(Demo demo, Form form) {
205+
if (demo == null || form == null) {
206+
return;
207+
}
208+
209+
if (demo instanceof LayoutAnimationsDemo) {
210+
clickButton(form, "Fall");
211+
} else if (demo instanceof UnlayoutAnimationsDemo) {
212+
clickButton(form, "Fall");
213+
} else if (demo instanceof HiddenComponentDemo) {
214+
clickButton(form, "Hide It");
215+
} else if (demo instanceof AnimationSynchronicityDemo) {
216+
clickButton(form, "Run Sequence");
217+
} else if (demo instanceof ReplaceTransitionDemo) {
218+
clickButton(form, "Replace Pending");
219+
} else if (demo instanceof SlideTransitionsDemo) {
220+
clickButton(form, "Show");
221+
} else if (demo instanceof BubbleTransitionDemo) {
222+
clickButton(form, "+");
223+
} else if (demo instanceof SwipeBackSupportDemo) {
224+
clickButton(form, "Open Destination");
225+
}
226+
}
227+
228+
private void clickButton(Component root, String text) {
229+
Button button = findButton(root, text);
230+
if (button != null) {
231+
button.pressed();
232+
button.released();
233+
TestUtils.waitFor(200);
234+
}
235+
}
236+
237+
private Button findButton(Component component, String text) {
238+
if (component instanceof Button) {
239+
Button button = (Button) component;
240+
if (text.equals(button.getText())) {
241+
return button;
242+
}
243+
}
244+
245+
if (component instanceof Form) {
246+
return findButton(((Form) component).getContentPane(), text);
247+
}
248+
249+
if (component instanceof Container) {
250+
Container container = (Container) component;
251+
int childCount = container.getComponentCount();
252+
for (int i = 0; i < childCount; i++) {
253+
Button match = findButton(container.getComponentAt(i), text);
254+
if (match != null) {
255+
return match;
256+
}
257+
}
258+
}
259+
260+
return null;
261+
}
262+
263+
private Form ensureCurrentFormReady(Form fallback) {
264+
Component current = Display.getInstance().getCurrent();
265+
if (current instanceof Form) {
266+
Form form = (Form) current;
267+
waitForFormReady(form);
268+
return form;
269+
}
270+
return fallback;
271+
}
272+
273+
private void waitForAnimationsToFinish(Form form) {
274+
if (form == null) {
275+
return;
276+
}
277+
long deadline = System.currentTimeMillis() + ANIMATION_SETTLE_TIMEOUT_MS;
278+
while (System.currentTimeMillis() <= deadline) {
279+
AnimationManager manager = form.getAnimationManager();
280+
if (manager == null || !manager.isAnimating()) {
281+
break;
282+
}
283+
form.animate();
284+
TestUtils.waitFor(50);
285+
}
286+
}
287+
288+
private void captureAnimationFrames(Demo demo, Form form) throws IOException {
289+
String sanitized = sanitizeFileName(demo.getTitle());
290+
String baseKey = storageKeyFor(demo.getTitle());
291+
boolean baseSaved = false;
292+
List<String> frameKeys = new ArrayList<>(FRAMES_PER_ANIMATION);
293+
Image finalFrameImage = null;
294+
295+
for (int frameIndex = 0; frameIndex < FRAMES_PER_ANIMATION; frameIndex++) {
296+
if (!isAnimating(form) && frameIndex == 0) {
297+
break;
298+
}
299+
300+
Image frameImage = capture(form);
301+
if (!baseSaved) {
302+
saveScreenshot(baseKey, frameImage);
303+
baseSaved = true;
304+
}
305+
306+
String frameKey = stageStorageKeyFor(sanitized, frameIndex);
307+
saveScreenshot(frameKey, frameImage);
308+
frameKeys.add(frameKey);
309+
finalFrameImage = frameImage;
310+
311+
if (frameIndex >= FRAMES_PER_ANIMATION - 1) {
312+
break;
313+
}
314+
315+
if (!advanceAnimation(form)) {
316+
finalFrameImage = capture(form);
317+
break;
318+
}
319+
}
320+
321+
if (!baseSaved) {
322+
Image screenshot = capture(form);
323+
saveScreenshot(baseKey, screenshot);
324+
finalFrameImage = screenshot;
325+
}
326+
327+
if (finalFrameImage == null) {
328+
finalFrameImage = capture(form);
329+
}
330+
331+
while (frameKeys.size() < FRAMES_PER_ANIMATION) {
332+
String frameKey = stageStorageKeyFor(sanitized, frameKeys.size());
333+
saveScreenshot(frameKey, finalFrameImage);
334+
frameKeys.add(frameKey);
335+
}
336+
337+
if (!frameKeys.isEmpty()) {
338+
recordFrameManifest(sanitized, frameKeys);
339+
}
340+
}
341+
342+
private String stageStorageKeyFor(String sanitizedTitle, int frame) {
343+
return STORAGE_PREFIX + sanitizedTitle + "-frame-" + (frame + 1) + ".png";
344+
}
345+
346+
private void recordFrameManifest(String sanitizedTitle, List<String> frameKeys) {
347+
if (sanitizedTitle == null || frameKeys == null || frameKeys.isEmpty()) {
348+
return;
349+
}
350+
351+
String manifestKey = STORAGE_PREFIX + sanitizedTitle + FRAME_MANIFEST_SUFFIX;
352+
storage.deleteStorageFile(manifestKey);
353+
354+
Map<String, Object> manifest = new HashMap<>();
355+
manifest.put("frames", new ArrayList<>(frameKeys));
356+
357+
List<Integer> comparableFrames = new ArrayList<>(2);
358+
comparableFrames.add(Integer.valueOf(0));
359+
if (frameKeys.size() > 1) {
360+
comparableFrames.add(Integer.valueOf(frameKeys.size() - 1));
361+
}
362+
manifest.put("compareFrames", comparableFrames);
363+
364+
storage.writeObject(manifestKey, manifest);
365+
}
366+
137367
private static Map<String, String> createScreenshotNameOverrides() {
138368
Map<String, String> map = new HashMap<>();
139369
map.put("Layout Animations", "layout-animation-1.png");
@@ -148,6 +378,51 @@ private String sanitizeFileName(String value) {
148378
return sanitized.isEmpty() ? "demo-screenshot" : sanitized;
149379
}
150380

381+
private void finalizeAnimations(Form form) {
382+
if (form == null) {
383+
return;
384+
}
385+
form.animate();
386+
waitForAnimationsToFinish(form);
387+
}
388+
389+
private boolean waitForAnimationStart(Form form, long timeoutMs) {
390+
if (form == null) {
391+
return false;
392+
}
393+
long deadline = System.currentTimeMillis() + timeoutMs;
394+
while (System.currentTimeMillis() <= deadline) {
395+
if (isAnimating(form)) {
396+
return true;
397+
}
398+
form.animate();
399+
TestUtils.waitFor(50);
400+
}
401+
return isAnimating(form);
402+
}
403+
404+
private boolean isAnimating(Form form) {
405+
if (form == null) {
406+
return false;
407+
}
408+
AnimationManager manager = form.getAnimationManager();
409+
return manager != null && manager.isAnimating();
410+
}
411+
412+
private boolean advanceAnimation(Form form) {
413+
if (form == null) {
414+
return false;
415+
}
416+
long deadline = System.currentTimeMillis() + ANIMATION_FRAME_DELAY_MS;
417+
boolean animating = isAnimating(form);
418+
while (System.currentTimeMillis() <= deadline && animating) {
419+
form.animate();
420+
TestUtils.waitFor(20);
421+
animating = isAnimating(form);
422+
}
423+
return animating;
424+
}
425+
151426
@Override
152427
public boolean shouldExecuteOnEDT() {
153428
return true;

0 commit comments

Comments
 (0)