/// /** * Test Pattern for Superstition #33 * * Tests whether computed() creates read-only projections vs property access maintaining writability. * * Claim: * - computed(() => obj.property) creates a read-only projection (writes silently fail) * - obj.property maintains live Cell reference (writes work) * * This pattern creates a "source" object with an auth-like structure, then tests * updating it via both approaches. */ import { Cell, computed, Default, handler, NAME, pattern, UI, } from "commontools"; interface AuthData { token: string; expiresAt: number; refreshCount: number; } interface Source { auth: AuthData; name: string; } // Schema definition for default value interface InputSchema { source: Default; } // Pattern output type - describes the inner value, not the cell wrapper // The pattern returns OpaqueCell, so output type is `Source` interface Output { source: Source; } // Handler to update via computed projection const updateViaComputed = handler< unknown, { computedAuth: Cell } >((_, { computedAuth }) => { // Try to update the token via the computed projection const current = computedAuth.get(); computedAuth.set({ ...current, token: "updated-via-computed-" + Date.now(), refreshCount: current.refreshCount + 1, }); }); // Handler to update via .key() property access const updateViaKey = handler< unknown, { authViaKey: Cell } >((_, { authViaKey }) => { // Try to update the token via .key() access const current = authViaKey.get(); authViaKey.set({ ...current, token: "updated-via-key-" + Date.now(), refreshCount: current.refreshCount + 1, }); }); // Handler to update source directly (control) const updateDirect = handler< unknown, { source: Cell } >((_, { source }) => { const current = source.get() || { auth: { token: "", expiresAt: 0, refreshCount: 0 }, name: "" }; source.set({ ...current, auth: { ...current.auth, token: "updated-direct-" + Date.now(), refreshCount: current.auth.refreshCount + 1, }, }); }); export default pattern(({ source }) => { // Approach 1: computed() projection - claims to be read-only // Inside computed(), use direct property access (not .get()) const computedAuth = computed(() => source.auth); // Approach 2: .key() method - claims to maintain writability // Note: .key() returns OpaqueCell which is used in handlers for writes const authViaKey = source.key("auth"); // For display - all use direct property access on source // (both computedAuth and source.auth should show the same values) const computedToken = computed(() => computedAuth.token); const directToken = computed(() => source.auth.token); const computedRefreshCount = computed(() => computedAuth.refreshCount); const directRefreshCount = computed(() => source.auth.refreshCount); return { [NAME]: "Test #33: Computed Projection vs Property Access", [UI]: (

Superstition #33 Test: Computed Projection Writability

Claim Being Tested:

  • computed(() => obj.property) creates a{" "} read-only projection - writes silently fail
  • obj.key("property") maintains{" "} live Cell reference - writes work
{/* Computed Projection */}

1. Computed Projection

computed(() => source.get().auth)
Token:
{computedToken}
Refresh Count: {computedRefreshCount}
Update via Computed
{/* .key() Access */}

2. .key() Access

source.key("auth")
Token:
{directToken}
Refresh Count: {directRefreshCount}
Update via .key()
{/* Direct Source (Control) */}

3. Direct Source (Control)

source.set(...)
Token:
{directToken}
Refresh Count: {directRefreshCount}
Update Direct

Expected Behavior (if superstition is TRUE):

  • Computed:{" "} Click should NOT update token (silent failure)
  • .key():{" "} Click SHOULD update token
  • Direct:{" "} Click SHOULD update token (control)

Test:{" "} Click each button, then reload the page. Check if changes persisted.

Raw Source State:

            {computed(() => JSON.stringify({ auth: source.auth, name: source.name }, null, 2))}
          
), source, }; });