|
| 1 | +import { M } from '@endo/patterns'; |
1 | 2 | import { |
2 | 3 | annihilate, |
3 | 4 | getBaggage, |
@@ -123,3 +124,80 @@ test.serial('vatData migrate to durableZone', t => { |
123 | 124 | const baggage2 = getBaggage(); |
124 | 125 | testSecondZoneIncarnation(t, makeDurableZone(baggage2)); |
125 | 126 | }); |
| 127 | + |
| 128 | +test.serial('exoClass stateShape expansion', t => { |
| 129 | + annihilate(); |
| 130 | + |
| 131 | + // See ../../swingset-liveslots/test/virtual-objects/state-shape.test.js |
| 132 | + const stateShapeMismatch = { message: /stateShape mismatch/ }; |
| 133 | + const HolderI = M.interface('Holder', { |
| 134 | + get: M.call().rest(M.arrayOf(M.string())).returns(M.record()), |
| 135 | + set: M.call(M.record()).returns(), |
| 136 | + }); |
| 137 | + const initHolder = fields => ({ ...fields }); |
| 138 | + const holderMethods = { |
| 139 | + get(...fields) { |
| 140 | + const { state } = this; |
| 141 | + // We require fields to be explicit because they are currently defined on |
| 142 | + // the state *prototype*. |
| 143 | + return Object.fromEntries( |
| 144 | + fields.flatMap(key => (key in state ? [[key, state[key]]] : [])), |
| 145 | + ); |
| 146 | + }, |
| 147 | + set(fields) { |
| 148 | + Object.assign(this.state, fields); |
| 149 | + }, |
| 150 | + }; |
| 151 | + const prepareHolder = (zone, stateShape) => |
| 152 | + zone.exoClass('Holder', HolderI, initHolder, holderMethods, { stateShape }); |
| 153 | + |
| 154 | + const fields = ['foo', 'bar', 'baz']; // but "baz" is not initially present |
| 155 | + const baggage1 = getBaggage(); |
| 156 | + const zone1 = makeDurableZone(baggage1); |
| 157 | + const makeHolder1 = prepareHolder(zone1, { |
| 158 | + foo: M.number(), |
| 159 | + bar: M.number(), |
| 160 | + }); |
| 161 | + const holder1 = makeHolder1({ foo: 0, bar: 1 }); |
| 162 | + t.deepEqual(holder1.get(...fields), { foo: 0, bar: 1 }); |
| 163 | + holder1.set({ foo: 2, bar: 2 }); |
| 164 | + t.deepEqual(holder1.get(...fields), { foo: 2, bar: 2 }); |
| 165 | + t.throws(() => makeHolder1({ foo: 0, bar: 1, baz: 2 })); |
| 166 | + t.throws(() => makeHolder1({ foo: 0, bar: 'string' })); |
| 167 | + |
| 168 | + nextLife(); |
| 169 | + t.throws( |
| 170 | + () => |
| 171 | + prepareHolder(makeDurableZone(getBaggage()), { |
| 172 | + foo: M.string(), |
| 173 | + bar: M.number(), |
| 174 | + }), |
| 175 | + stateShapeMismatch, |
| 176 | + 'backwards-incompatible stateShape change', |
| 177 | + ); |
| 178 | + |
| 179 | + nextLife(); |
| 180 | + t.throws( |
| 181 | + () => |
| 182 | + prepareHolder(makeDurableZone(getBaggage()), { |
| 183 | + foo: M.or(M.number(), M.string()), |
| 184 | + bar: M.number(), |
| 185 | + baz: M.or(undefined, M.number()), |
| 186 | + }), |
| 187 | + stateShapeMismatch, |
| 188 | + 'stateShape field value expansion (needs #7407)', |
| 189 | + ); |
| 190 | + |
| 191 | + nextLife(); |
| 192 | + const baggage2 = getBaggage(); |
| 193 | + const zone2 = makeDurableZone(baggage2); |
| 194 | + const makeHolder2 = prepareHolder(zone2, { |
| 195 | + foo: M.number(), |
| 196 | + bar: M.number(), |
| 197 | + baz: M.or(undefined, M.number()), |
| 198 | + }); |
| 199 | + const holder2 = makeHolder2({ foo: 0, bar: 1, baz: 2 }); |
| 200 | + t.deepEqual(holder2.get(...fields), { foo: 0, bar: 1, baz: 2 }); |
| 201 | + holder2.set({ foo: 2, bar: 2, baz: undefined }); |
| 202 | + t.deepEqual(holder2.get(...fields), { foo: 2, bar: 2, baz: undefined }); |
| 203 | +}); |
0 commit comments