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
10 changes: 8 additions & 2 deletions src/runtime/proxy-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,10 +378,16 @@ export const proxyComponent = (
return;
}

const propDesc = Object.getOwnPropertyDescriptor(prototype, propName);
// special handling of boolean attributes. Null (removal) means false.
// everything else means true (including an empty string
const propFlags = members.find(([m]) => m === propName);
if (propFlags && propFlags[1][0] & MEMBER_FLAGS.Boolean) {
(newValue as any) = newValue === null || newValue === 'false' ? false : true;
}

// test whether this property either has no 'getter' or if it does, does it also have a 'setter'
// before attempting to write back to component props
newValue = newValue === null && typeof this[propName] === 'boolean' ? (false as any) : newValue;
const propDesc = Object.getOwnPropertyDescriptor(prototype, propName);
if (newValue != this[propName] && (!propDesc.get || !!propDesc.set)) {
this[propName] = newValue;
}
Expand Down
62 changes: 62 additions & 0 deletions src/runtime/test/attr.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ describe('attribute', () => {

expect(root.textContent).toBe('false');
expect(root.bool).toBe(false);

// reset
root.setAttribute('bool', '');
expect(root.bool).toBe(true);

// check setAttribute
root.setAttribute('bool', 'false');
expect(root.bool).toBe(false);
});

it('set boolean, undefined when missing attribute', async () => {
Expand Down Expand Up @@ -111,6 +119,14 @@ describe('attribute', () => {

expect(root.textContent).toBe('true');
expect(root.bool).toBe(true);

// reset
root.removeAttribute('bool');
expect(root.bool).toBe(false);

// check setAttribute
root.setAttribute('bool', 'true');
expect(root.bool).toBe(true);
});

it('set boolean true from no attribute value', async () => {
Expand All @@ -133,6 +149,14 @@ describe('attribute', () => {

expect(root.textContent).toBe('true');
expect(root.bool).toBe(true);

// reset
root.removeAttribute('bool');
expect(root.bool).toBe(false);

// check setAttribute
(root as HTMLElement).setAttribute('bool', '');
expect(root.bool).toBe(true);
});

it('set boolean true from empty string', async () => {
Expand All @@ -155,6 +179,44 @@ describe('attribute', () => {

expect(root.textContent).toBe('true');
expect(root.bool).toBe(true);

// reset
root.removeAttribute('bool');
expect(root.bool).toBe(false);

// check setAttribute
root.setAttribute('bool', '');
expect(root.bool).toBe(true);
});

it('set boolean true from any other string apart from "false"', async () => {
@Component({ tag: 'cmp-a' })
class CmpA {
@Prop() bool: boolean;
render() {
return `${this.bool}`;
}
}

const { root } = await newSpecPage({
components: [CmpA],
html: `<cmp-a bool="nice"></cmp-a>`,
});

expect(root).toEqualHtml(`
<cmp-a bool="nice">true</cmp-a>
`);

expect(root.textContent).toBe('true');
expect(root.bool).toBe(true);

// reset
root.removeAttribute('bool');
expect(root.bool).toBe(false);

// check setAttribute
root.setAttribute('bool', 'anything');
expect(root.bool).toBe(true);
});

it('set zero', async () => {
Expand Down
Loading