Skip to content

Commit

Permalink
feat(template-compiler): lwc:if, lwc:elseif, lwc:else directives (#3030)
Browse files Browse the repository at this point in the history
* feat: lwc:if, lwc:elseif, and lwc:else directives (#2985)

* feat: refactor parser context to support new behavior for slot name tracking (#2990)

* feat(template-compiler): add codegen portion for else-if directives (#2995)

* fix: use Fragment VNodes for if-elseif-else children (#3047)

Co-authored-by: Pierre-Marie Dartus <p.dartus@salesforce.com>
Co-authored-by: James Tu <j.tu@salesforce.com>
  • Loading branch information
3 people authored Oct 5, 2022
1 parent a89fcd7 commit edab2dd
Show file tree
Hide file tree
Showing 202 changed files with 10,958 additions and 60 deletions.
2 changes: 1 addition & 1 deletion packages/@lwc/errors/src/compiler/error-info/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
/**
* Next error code: 1159
* Next error code: 1167
*/

export * from './compiler';
Expand Down
50 changes: 50 additions & 0 deletions packages/@lwc/errors/src/compiler/error-info/template-transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -667,4 +667,54 @@ export const ParserDiagnostics = {
level: DiagnosticLevel.Error,
url: '',
},
IF_BLOCK_DIRECTIVE_SHOULD_BE_EXPRESSION: {
code: 1159,
message: 'lwc:if directive value should be an expression',
level: DiagnosticLevel.Error,
url: '',
},
ELSEIF_BLOCK_DIRECTIVE_SHOULD_BE_EXPRESSION: {
code: 1160,
message: 'lwc:elseif directive value should be an expression',
level: DiagnosticLevel.Error,
url: '',
},
ELSE_BLOCK_DIRECTIVE_CANNOT_HAVE_VALUE: {
code: 1161,
message: 'lwc:else directive cannot have a value',
level: DiagnosticLevel.Error,
url: '',
},
INVALID_IF_BLOCK_DIRECTIVE_WITH_CONDITIONAL: {
code: 1162,
message: "Invalid usage of 'lwc:if' and '{0}' directives on the same element.",
level: DiagnosticLevel.Error,
url: '',
},
INVALID_ELSEIF_BLOCK_DIRECTIVE_WITH_CONDITIONAL: {
code: 1163,
message: "Invalid usage of 'lwc:elseif' and '{0}' directives on the same element.",
level: DiagnosticLevel.Error,
url: '',
},
INVALID_ELSE_BLOCK_DIRECTIVE_WITH_CONDITIONAL: {
code: 1164,
message: "Invalid usage of 'lwc:else' and '{0}' directives on the same element.",
level: DiagnosticLevel.Error,
url: '',
},
LWC_IF_SCOPE_NOT_FOUND: {
code: 1165,
message:
"'{0}' directive must be used immediately after an element with 'lwc:if' or 'lwc:elseif'. No such element found.",
level: DiagnosticLevel.Error,
url: '',
},
LWC_IF_CANNOT_BE_USED_WITH_IF_DIRECTIVE: {
code: 1166,
message:
"'{0}' directive cannot be used with 'lwc:if', 'lwc:elseif', or 'lwc:else directives on the same element.",
level: DiagnosticLevel.Error,
url: '',
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { createElement } from 'lwc';
import XComplex from 'x/complex';
import XTest from 'x/test';
import XForEach from 'x/forEach';

describe('lwc:if, lwc:elseif, lwc:else directives', () => {
it('should render if branch if the value for lwc:if is truthy', () => {
const elm = createElement('x-test', { is: XTest });
elm.showIf = true;
document.body.appendChild(elm);

expect(elm.shadowRoot.querySelector('.if')).not.toBeNull();
});

it('should render elseif branch if the value for lwc:if is falsy and the value for lwc:elseif is truthy', () => {
const elm = createElement('x-test', { is: XTest });
elm.showElseif = true;
document.body.appendChild(elm);

expect(elm.shadowRoot.querySelector('.elseif')).not.toBeNull();
});

it('should render else branch if the values for lwc:if and lwc:elseif are all falsy', () => {
const elm = createElement('x-test', { is: XTest });
document.body.appendChild(elm);

expect(elm.shadowRoot.querySelector('.else')).not.toBeNull();
});

it('should update which branch is rendered if the value changes', () => {
const elm = createElement('x-test', { is: XTest });
elm.showIf = true;
document.body.appendChild(elm);

expect(elm.shadowRoot.querySelector('.if')).not.toBeNull();

elm.showIf = false;
return Promise.resolve()
.then(() => {
expect(elm.shadowRoot.querySelector('.else')).not.toBeNull();
elm.showElseif = true;
})
.then(() => {
expect(elm.shadowRoot.querySelector('.elseif')).not.toBeNull();
elm.showIf = true;
})
.then(() => {
expect(elm.shadowRoot.querySelector('.if')).not.toBeNull();
});
});

it('should render content when nested inside another if branch', () => {
const element = createElement('x-complex', { is: XComplex });
element.showNestedContent = true;
document.body.appendChild(element);

expect(element.shadowRoot.querySelector('.nestedContent')).not.toBeNull();
});

it('should rerender content when nested inside another if branch', () => {
const element = createElement('x-complex', { is: XComplex });
document.body.appendChild(element);

expect(element.shadowRoot.querySelector('.nestedElse')).not.toBeNull();

element.showNestedContent = true;
return Promise.resolve().then(() => {
expect(element.shadowRoot.querySelector('.nestedContent')).not.toBeNull();
});
});

it('should render list content properly', () => {
const element = createElement('x-complex', { is: XComplex });
element.showList = true;
document.body.appendChild(element);

expect(element.shadowRoot.querySelector('.if').textContent).toBe('123');
});

it('should rerender list content when updated', () => {
const element = createElement('x-for-each', { is: XForEach });
element.showList = true;
document.body.appendChild(element);

expect(element.shadowRoot.querySelector('.if').textContent).toBe('123');

element.appendToList({
value: 4,
show: true,
});

return Promise.resolve()
.then(() => {
expect(element.shadowRoot.querySelector('.if').textContent).toBe('1234');

element.showList = false;
element.appendToList({
value: 5,
show: true,
});
element.prependToList({
value: 0,
show: true,
});
})
.then(() => {
expect(element.shadowRoot.querySelector('.if')).toBeNull();

element.showList = true;
})
.then(() => {
expect(element.shadowRoot.querySelector('.if').textContent).toBe('012345');
});
});

it('should rerender list items when conditional expressions change', () => {
const element = createElement('x-for-each', { is: XForEach });
element.showList = true;
document.body.appendChild(element);

expect(element.shadowRoot.querySelector('.if').textContent).toBe('123');

element.appendToList({
value: 4,
show: false,
});

return Promise.resolve()
.then(() => {
expect(element.shadowRoot.querySelector('.if').textContent).toBe('123');

element.show(4);
})
.then(() => {
expect(element.shadowRoot.querySelector('.if').textContent).toBe('1234');

element.hide(1);
element.hide(3);
element.prependToList({
value: 0,
show: true,
});
})
.then(() => {
expect(element.shadowRoot.querySelector('.if').textContent).toBe('024');
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<template>
<div><h1>Header</h1></div>
<div lwc:if={showList} class="if">
<template for:each={items} for:item="item">
<div key={item}>{item}</div>
</template>
</div>
<div lwc:else class="else">
<template lwc:if={showNestedContent}>
<div class="nestedContent">Nested Content</div>
</template>
<template lwc:else>
<div class="nestedElse">Nothing for you</div>
</template>
<div>Nested Footer</div>
</div>
<div><h1>Footer</h1></div>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { LightningElement, api, track } from 'lwc';

export default class Complex extends LightningElement {
@api showNestedContent = false;
@api showList = false;
@track items = [1, 2, 3];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<template>
<div lwc:if={showList} class="if">
<template for:each={items} for:item="item">
<template lwc:if={item.show}>
<div key={item.value}>{item.value}</div>
</template>
</template>
</div>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { LightningElement, api, track } from 'lwc';

export default class ForEach extends LightningElement {
@api showList = false;
@track items = [
{ value: 1, show: true },
{ value: 2, show: true },
{ value: 3, show: true },
];

@api
appendToList(value) {
this.items.push(value);
}

@api
prependToList(value) {
this.items.splice(0, 0, value);
}

@api
hide(value) {
this.find(value).show = false;
}

@api
show(value) {
this.find(value).show = true;
}

find(value) {
return this.items.find((item) => item.value === value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<template>
<template lwc:if={showIf}>
<p class="if">If!</p>
</template>
<template lwc:elseif={showElseif}>
<p class="elseif">Else If!</p>
</template>
<template lwc:else>
<p class="else">Else!</p>
</template>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { LightningElement, api } from 'lwc';

export default class Test extends LightningElement {
@api showIf = false;
@api showElseif = false;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<template>
<template for:each={items} for:item="item">
<div lwc:if={item.visible} key={item.key}>Conditional Iteration</div>
<div lwc:else key={item.key}>Else</div>
</template>
</template>
Loading

0 comments on commit edab2dd

Please sign in to comment.