diff --git a/src/sinks/class-sink.test.ts b/src/sinks/class-sink.test.ts index 51bbe1f..69d9f6e 100644 --- a/src/sinks/class-sink.test.ts +++ b/src/sinks/class-sink.test.ts @@ -30,35 +30,92 @@ describe('Class Sink', () => { describe('Given a class object', () => { - it('sets classes for truthy attributes on sink', () => { - const el = MockElement(); - const sink = ClassObjectSink(el); + describe('when a property of the object is a present value', () => { + + it('sets classes for truthy attributes on sink', () => { + const el = MockElement(); + const sink = ClassObjectSink(el); + + sink({ + class1: true, + class2: 1, + class3: 'yes!', + }); + expect(el.className).toContain('class1'); + expect(el.className).toContain('class2'); + expect(el.className).toContain('class3'); + }); - sink({ - class1: true, - class2: 1, - class3: 'yes!', + it('clears classes for falsy attributes on sink', () => { + const el = MockElement({ className: 'class1 class2 class3' }); + const sink = ClassObjectSink(el); + expect(el.className).toContain('class1'); + expect(el.className).toContain('class2'); + expect(el.className).toContain('class3'); + + sink({ + class1: false, + class2: 0, + class3: '', + }); + expect(el.className).not.toContain('class1'); + expect(el.className).not.toContain('class2'); + expect(el.className).not.toContain('class3'); }); - expect(el.className).toContain('class1'); - expect(el.className).toContain('class2'); - expect(el.className).toContain('class3'); + + it('should not add class when value is false', () => { + const el = MockElement(); + const sink = ClassObjectSink(el); + + sink({ + dotted: false, + }); + + expect(el.className).not.toContain('dotted'); + expect(el.className).toEqual(''); + }); + }); - it('clears classes for falsy attributes on sink', () => { - const el = MockElement({ className: 'class1 class2 class3' }); - const sink = ClassObjectSink(el); - expect(el.className).toContain('class1'); - expect(el.className).toContain('class2'); - expect(el.className).toContain('class3'); + describe('when a property of the object is a future value', () => { + + describe('when it resolves/emits false', () => { + + it('should not add a class corresponding to the property name', async () => { + const el = MockElement(); + const sink = ClassObjectSink(el); + + const dottedPromise = Promise.resolve(false); + sink({ + dotted: dottedPromise, + }); + + await dottedPromise; + + expect(el.className).not.toContain('dotted'); + expect(el.className).toEqual(''); + }); - sink({ - class1: false, - class2: 0, - class3: '', }); - expect(el.className).not.toContain('class1'); - expect(el.className).not.toContain('class2'); - expect(el.className).not.toContain('class3'); + + describe('when it resolves/emits true', () => { + + it('should add a class corresponding to the property name', async () => { + const el = MockElement(); + const sink = ClassObjectSink(el); + + const activePromise = Promise.resolve(true); + sink({ + active: activePromise, + }); + + await activePromise; + + expect(el.className).toContain('active'); + }); + + }); + }); }); diff --git a/src/sinks/class-sink.ts b/src/sinks/class-sink.ts index ac699a4..a0688df 100644 --- a/src/sinks/class-sink.ts +++ b/src/sinks/class-sink.ts @@ -35,7 +35,12 @@ export const ClassObjectSink: Sink = (node: Element) => { // FIXME: is it safe to assume it's an object, at this point? : (<(ClassName | ClassRecord)[]>[]).concat(name).forEach(obj => Object.entries(obj) // TODO: support 3-state with toggle - .forEach(([k, v]) => asap(v ? add : remove, k)) + .forEach(([k, v]) => { + // Use asap to handle both present and future values + // For futures (Promise/Observable), it will wait for resolution + // For present values, it will execute immediately + asap((resolvedValue: any) => resolvedValue ? add(k) : remove(k), v); + }) ) }; };