Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 80 additions & 23 deletions src/sinks/class-sink.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(<HTMLElement>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(<HTMLElement>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(<HTMLElement>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(<HTMLElement>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(<HTMLElement>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(<HTMLElement>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(<HTMLElement>el);

const activePromise = Promise.resolve(true);
sink({
active: activePromise,
});

await activePromise;

expect(el.className).toContain('active');
});

});

});

});
Expand Down
7 changes: 6 additions & 1 deletion src/sinks/class-sink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,12 @@ export const ClassObjectSink: Sink<Element> = (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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was it not feasible to use subscribe?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked into using subscribe, but it requires a node parameter as the first argument:

export const subscribe = (node: Node, source: MaybeFuture<T>, next: ...) => { ... }

Since we're inside the forEach callback and don't have the node in scope at that point, asap seemed like the right fit. It's also the pattern used in other sinks like content-sink.ts, dataset-sink.ts, and style-sink.ts.

Should I refactor this differently?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, yeah, you're right... good point.
So, we're all good, then, the rest looks great, LGTM!

})
)
};
};
Expand Down