', 1)[0]
self.assertIn('stroke="var(--icon-palette-roles-active-3-', card('bounding_solid'))
self.assertIn('stroke="var(--icon-palette-accents-sketch-active-3-', card('extend'))
self.assertIn('stroke="var(--icon-palette-roles-guide-', card('draft'))
def test_live_palette_tokens_save_only_display_colours_and_reject_stale_or_invalid_changes(self):
with review_fixture() as (root, design, get, post):
palette_path = design / 'palette.toml'
original = palette_path.read_text()
palette = tomllib.loads(original)
state = json.loads(get('/api/palette'))
tokens = {token['id']: token for token in state['tokens']}
self.assertIn('roles.ink', tokens)
self.assertIn('accents.surface.active-2', tokens)
self.assertIn('variants.red-green.sketch.active-3', tokens)
self.assertNotIn('accents.construction.guide', tokens, 'guides have one shared colour')
self.assertNotIn('roles.ink.source', tokens)
self.assertTrue(tokens['roles.gap']['knockout'])
accepted = (design / 'review.json').read_bytes()
artwork = (design / 'svg/model/box.svg').read_bytes()
def save(revision, changes):
return post({'revision': revision, 'changes': changes}, endpoint='/api/palette')
replacement = '#123456' if tokens['roles.ink']['dark'] != '#123456' else '#654321'
paints = {'light': tokens['roles.ink']['light'], 'dark': replacement}
changes = {'roles.ink': paints,
'accents.surface.active-2': {'light': tokens['accents.surface.active-2']['light'], 'dark': '#bc789a'},
'variants.red-green.sketch.active-3': {'light': tokens['variants.red-green.sketch.active-3']['light'], 'dark': '#778899'}}
saved = save(state['revision'], changes)
self.assertTrue(saved['ok'])
expected = tomllib.loads(original)
for key, colours in changes.items():
target = expected
for part in key.split('.'): target = target[part]
target.update(colours)
self.assertEqual(tomllib.loads(palette_path.read_text()), expected)
self.assertIn('# Icon colour roles.', palette_path.read_text(), 'comments remain intact')
self.assertEqual(tomllib.loads(palette_path.read_text())['roles']['ink']['source'], palette['roles']['ink']['source'])
for revision, changes in [
(state['revision'], {'roles.ink': paints}),
(saved['revision'], {'roles.ink.source': paints}),
(saved['revision'], {'accents.construction.guide': paints}),
(saved['revision'], {'roles.gap': paints}),
(saved['revision'], {'roles.ink': {'light': 'url(evil)', 'dark': replacement}}),
]:
before = palette_path.read_bytes()
with self.assertRaises(urllib.error.HTTPError): save(revision, changes)
self.assertEqual(palette_path.read_bytes(), before)
page = get('/')
self.assertIn('var(--icon-palette-roles-line-light,', page)
self.assertIn('--icon-palette-roles-ink-dark:' + replacement, page)
self.assertIn('id="palette-tokens"', page)
self.assertIn('input.oninput', page)
self.assertIn('body.palette-preview .card.final .art{opacity:1}', page)
self.assertEqual((design / 'review.json').read_bytes(), accepted)
self.assertEqual((design / 'svg/model/box.svg').read_bytes(), artwork)
self.assertFalse(list(design.glob('*.tmp')))
def test_knockouts_mask_only_preceding_layers_and_preserve_later_foreground(self):
with review_fixture() as (root, design, get, post):
source = '''
'''
path = design / 'svg/model/cutout.svg'
path.write_text(source)
accepted = (design / 'review.json').read_bytes()
page = get('/')
fragments = [svg for svg in re.findall(r'