1# Native page movement controls
2
3Nine OneNote 2010 reference outcomes from the synthetic `04-nested` section.
4Every outcome has a closed notebook, native page XML, an independent cold reopen,
5and saved desktop actions/screenshots. `provenance.json` records the controllers,
6source, machines and teardown. Identical files link to one stored copy.
7
8The controls distinguish three operations:
9
10- Moving an expanded, individually selected tab moves that page alone. Its
11 following subpages retain their levels and join the preceding series.
12- Moving an explicitly selected range moves every selected page. Moving a
13 collapsed top-level tab also moves its hidden subpages.
14- Promotion/demotion changes only the selected page's level. A level-3 page can
15 directly follow a level-1 page; indentation is not a conventional subtree.
16
17`tools/native_page_movement.py OUTPUT` replays the eight numbered phases in an
18owned 800×600 clone. The `single-page` reference comes from an earlier replay
19whose group-movement expectation was rejected; its actual single-page outcome
20was retained and independently validated. Its exact desktop actions are saved
21alongside the notebook. The subsequent ordinary-click replay reproduced it.
22Cold-capture each numbered phase with `tools/native_runner.py INPUT OUTPUT
23--expected-pages 9 --collect-notebook`.
24
25Run `PYTHONPATH=tools python3 -m unittest tools.test_page_movement -v` after
26building the document example. The test verifies stable page identities, order,
27levels, series membership, content-tree relationships, text and formatting,
28then compares native XML and all active graphs across each cold reopen.
29
30In `02-promoted-parent`, native OneNote left one section metadata copy at level 2
31while the authoritative page metadata was level 1. Its cold reopen refreshed
32that copy to 1. MS-ONE §2.2.81 product-behavior footnote 9 explicitly permits
33temporary differences and describes subsequent native refresh. This fixture
34accepts exactly one such level refresh; every other graph field must match.
35The default comparison still rejects this change and remains strict for Rust
36publication fixtures.