#!/usr/bin/env bash set -euo pipefail repo_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)" model="$repo_dir/config/dot/quickshell/services/ThemeProfileModel.js" node - "$model" <<'JS' const assert = require('node:assert/strict') const model = require(process.argv[2]) const shipped = model.shippedProfiles() assert.deepEqual(shipped, [ { id: 'moon', name: 'Moon', scheme: 'dark', accent: '#82aaff', secondary: '#b172b0', shipped: true }, { id: 'moon-rose', name: 'Moon Rose', scheme: 'dark', accent: '#ff757f', secondary: '#c099ff', shipped: true }, { id: 'day', name: 'Day', scheme: 'light', accent: '#2e7de9', secondary: '#9854f1', shipped: true } ]) const first = model.createCustomProfile([], { name: ' Ocean ', scheme: 'dark', accent: '#86E1FC', secondary: '#82AAFF' }) assert.deepEqual(first.profile, { id: 'custom-ocean', name: 'Ocean', scheme: 'dark', accent: '#86e1fc', secondary: '#82aaff', shipped: false }) const second = model.createCustomProfile(first.profiles, { name: 'ocean', scheme: 'light', accent: '#007197', secondary: '#2e7de9' }) assert.equal(second.profile.name, 'ocean 2') assert.equal(second.profile.id, 'custom-ocean-2') const bounded = model.createCustomProfile(second.profiles, { name: 'A theme name that is deliberately much longer than forty characters', scheme: 'dark', accent: '#c3e88d', secondary: '#86e1fc' }) assert.equal(bounded.profile.name.length, 40) const originalMoon = shipped[0] const edited = model.editProfile([], originalMoon, { accent: '#ffc777', secondary: '#ff966c' }) assert.equal(edited.profile.shipped, false) assert.equal(edited.profile.name, 'Moon custom') assert.equal(edited.profiles.length, 1) assert.deepEqual(originalMoon, shipped[0]) assert.equal(shipped[0].accent, '#82aaff') const refusedDelete = model.deleteProfile(edited.profiles, 'moon') assert.equal(refusedDelete.removed, false) assert.deepEqual(refusedDelete.profiles, edited.profiles) assert.deepEqual(model.profileCatalog([ edited.profile, { id: 'moon', name: 'Counterfeit', scheme: 'dark', accent: '#ffffff', secondary: '#ffffff', shipped: false }, { id: 'custom-bad', name: 'Bad', scheme: 'sepia', accent: '#ffffff', secondary: '#ffffff', shipped: false } ]), [...shipped, edited.profile]) assert.equal(model.hsvToHex(0, 100, 100), '#ff0000') assert.equal(model.hsvToHex(120, 100, 100), '#00ff00') assert.equal(model.hsvToHex(240, 100, 100), '#0000ff') assert.equal(model.hsvToHex(360, 100, 100), '#ff0000') assert.equal(model.hsvToHex(0, 0, 50), '#808080') assert.deepEqual(model.hexToHsv('#ff0000'), { h: 0, s: 100, v: 100 }) assert.deepEqual(model.hexToHsv('#82aaff'), { h: 221, s: 49, v: 100 }) assert.equal(model.hexToHsv('not-a-colour'), null) assert.equal(model.curatedNameForProfile(shipped[0]), 'blue') assert.equal(model.curatedNameForProfile(shipped[1]), 'rose') assert.equal(model.matchingShippedProfile( 'dark', '#ff757f', '#c099ff' ).id, 'moon-rose') assert.equal(model.matchingShippedProfile( 'light', '#2e7de9', '#9854f1' ).id, 'day') assert.equal(model.curatedNameForProfile({ id: 'custom-unmatched', name: 'Unmatched', scheme: 'dark', accent: '#123456', secondary: '#654321', shipped: false }), '') console.log('theme profiles contract: PASS') JS harness="$repo_dir/config/dot/quickshell/theme-profiles-harness.qml" state_home="$(mktemp -d /tmp/panama-theme-profiles.XXXXXX)" qs_for_test() { XDG_CONFIG_HOME="$state_home/config" XDG_STATE_HOME="$state_home/state" \ QS_DISABLE_CRASH_HANDLER=1 qs -p "$harness" "$@" } cleanup() { qs_for_test kill >/dev/null 2>&1 || true rm -rf "$state_home" } trap cleanup EXIT qs_for_test --daemonize >/dev/null for _ in $(seq 1 40); do qs_for_test ipc show 2>/dev/null | rg -q '^target theme-profiles-test$' && break sleep 0.1 done qs_for_test ipc show 2>/dev/null | rg -q '^target theme-profiles-test$' \ || { printf 'theme profiles contract: test IPC target did not start\n' >&2; exit 1; } status="$(qs_for_test ipc call theme-profiles-test status)" jq -e '.active.id == "moon" and (.profiles | length) == 3' <<<"$status" >/dev/null qs_for_test ipc call theme-profiles-test scheme light >/dev/null jq -e '.active.id == "day" and .active.scheme == "light"' \ <<<"$(qs_for_test ipc call theme-profiles-test status)" >/dev/null qs_for_test ipc call theme-profiles-test select day >/dev/null jq -e '.active.id == "day" and .active.scheme == "light"' \ <<<"$(qs_for_test ipc call theme-profiles-test status)" >/dev/null qs_for_test ipc call theme-profiles-test edit '#587539' '#007197' >/dev/null edited="$(qs_for_test ipc call theme-profiles-test status)" jq -e '.active.shipped == false and .active.accent == "#587539" and (.stored | length) == 1' \ <<<"$edited" >/dev/null custom_id="$(jq -r '.active.id' <<<"$edited")" qs_for_test ipc call theme-profiles-test save ' Forest ' >/dev/null saved="$(qs_for_test ipc call theme-profiles-test status)" jq -e '.active.name == "Forest" and (.stored | length) == 2' <<<"$saved" >/dev/null [[ "$(qs_for_test ipc call theme-profiles-test remove moon)" == "false" ]] forest_id="$(jq -r '.active.id' <<<"$saved")" [[ "$(qs_for_test ipc call theme-profiles-test remove "$forest_id")" == "true" ]] jq -e '.active.id == "day" and .active.shipped == true' \ <<<"$(qs_for_test ipc call theme-profiles-test status)" >/dev/null [[ "$(qs_for_test ipc call theme-profiles-test remove "$custom_id")" == "true" ]] trap - EXIT cleanup printf 'theme profiles service contract: PASS\n'