From a7d2edd0c9f77b1a8167d6a72a456910116abfeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Fri, 22 Mar 2019 11:09:57 +0100 Subject: [PATCH] imp: qweb compiler now condenses whitespaces This is a pretty big breaking change: the html output by qweb is now different. The main goal is to optimize the compiled templates: - condense all consecutive whitespaces into a single spaces - if possible, drop completely some text nodes, based on the following heuristic: if a text node is only composed of whitespaces, and contains at least one linebreak, then it can be dropped. This leads in my benchmark test to an improvement of about 10%, in rendering speed and in memory consuption. Note: whitespace here means anything that matches the \s regexp: newlines, tabs, ... close #8 --- doc/qweb.md | 8 + examples/todoapp/components/TodoApp.js | 3 +- .../ui/__snapshots__/home_menu.test.ts.snap | 23 +- .../ui/__snapshots__/navbar.test.ts.snap | 44 +-- .../__snapshots__/notification.test.ts.snap | 26 +- .../tests/ui/__snapshots__/root.test.ts.snap | 256 +----------------- src/qweb.ts | 19 ++ tests/__snapshots__/component.test.ts.snap | 8 - tests/__snapshots__/qweb.test.ts.snap | 191 ++++++------- tests/__snapshots__/store.test.ts.snap | 14 +- tests/qweb.test.ts | 45 +++ 11 files changed, 169 insertions(+), 468 deletions(-) diff --git a/doc/qweb.md b/doc/qweb.md index 7ee88951..3aee3e69 100644 --- a/doc/qweb.md +++ b/doc/qweb.md @@ -25,3 +25,11 @@ result = qweb.render("sometemplate", { name: "world" }); - t-on directive - t-widget, t-props, t-key - t-ref + +## Note on white spaces + +White spaces in a templates are handled in a special way: + +- consecutive whitespaces are always condensed to a single whitespace +- if a whitespace-only text node contains a linebreak, it is ignored +- the previous rules do not apply if we are in a `
` tag
diff --git a/examples/todoapp/components/TodoApp.js b/examples/todoapp/components/TodoApp.js
index ab3aca07..636630a9 100644
--- a/examples/todoapp/components/TodoApp.js
+++ b/examples/todoapp/components/TodoApp.js
@@ -33,7 +33,8 @@ class TodoApp extends Component {
   }
 
   get remainingText() {
-    return (this.remaining < 2 ? "item" : "items") + " left";
+    const items = this.remaining < 2 ? "item" : "items";
+    return ` ${items} left`;
   }
 
   addTodo(ev) {
diff --git a/examples/web/static/tests/ui/__snapshots__/home_menu.test.ts.snap b/examples/web/static/tests/ui/__snapshots__/home_menu.test.ts.snap
index 1b89d629..1ea0da1b 100644
--- a/examples/web/static/tests/ui/__snapshots__/home_menu.test.ts.snap
+++ b/examples/web/static/tests/ui/__snapshots__/home_menu.test.ts.snap
@@ -1,24 +1,3 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`can be rendered 1`] = `
-""
-`;
+exports[`can be rendered 1`] = `""`;
diff --git a/examples/web/static/tests/ui/__snapshots__/navbar.test.ts.snap b/examples/web/static/tests/ui/__snapshots__/navbar.test.ts.snap
index 92ae0b43..c59d87fd 100644
--- a/examples/web/static/tests/ui/__snapshots__/navbar.test.ts.snap
+++ b/examples/web/static/tests/ui/__snapshots__/navbar.test.ts.snap
@@ -1,45 +1,9 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`can be rendered (in home menu, no app) 1`] = `
-"
- - -
" -`; +exports[`can be rendered (in home menu, no app) 1`] = `"
"`; -exports[`can be rendered (in home menu, one active app) 1`] = ` -"
- - -
" -`; +exports[`can be rendered (in home menu, one active app) 1`] = `"
"`; -exports[`can render one menu item 1`] = ` -"
- - - - Discuss - -
    - - -
- -
" -`; +exports[`can render one menu item 1`] = `""`; -exports[`mobile mode: navbar is different 1`] = ` -"
- - - - Notes - -
    - -
  • MOBILEMODE
  • -
- -
" -`; +exports[`mobile mode: navbar is different 1`] = `"
Notes
  • MOBILEMODE
"`; diff --git a/examples/web/static/tests/ui/__snapshots__/notification.test.ts.snap b/examples/web/static/tests/ui/__snapshots__/notification.test.ts.snap index 778e21d7..b847fb7e 100644 --- a/examples/web/static/tests/ui/__snapshots__/notification.test.ts.snap +++ b/examples/web/static/tests/ui/__snapshots__/notification.test.ts.snap @@ -1,27 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`can be rendered (type = warning) 1`] = ` -"
- -
- - title -
-
- message -
-
" -`; +exports[`can be rendered (type = warning) 1`] = `"
title
message
"`; -exports[`can be rendered 1`] = ` -"
- -
- - title -
-
- message -
-
" -`; +exports[`can be rendered 1`] = `"
title
message
"`; diff --git a/examples/web/static/tests/ui/__snapshots__/root.test.ts.snap b/examples/web/static/tests/ui/__snapshots__/root.test.ts.snap index e01ee020..2fdecf1d 100644 --- a/examples/web/static/tests/ui/__snapshots__/root.test.ts.snap +++ b/examples/web/static/tests/ui/__snapshots__/root.test.ts.snap @@ -1,257 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`can be rendered (in home menu) 1`] = ` -"
-
- - -
- -
-
- -
-
Loading
-
" -`; +exports[`can be rendered (in home menu) 1`] = `""`; -exports[`clicks on client action with invalid key => empty widget is rendered + warning 1`] = ` -"
-
- - - - Discuss - -
    - - -
- -
- -
-
- -
- -
- - Invalid Client Action -
-
- Cannot find widget 'mail.discuss' in the action registry -
-
- -
-
Loading
-
" -`; +exports[`clicks on client action with invalid key => empty widget is rendered + warning 1`] = `"
Invalid Client Action
Cannot find widget 'mail.discuss' in the action registry
Loading
"`; -exports[`if url has action_id, will render action and navigate to proper menu_id 1`] = ` -"
-
- - - - Discuss - -
    - - -
- -
- -
-
-
    -
  1. - Discuss -
  2. -
-
-
- -
-
-
-
- -
-
-
- - -
-
-
-
- DISCUSS!! -
-
- - - - - - - -
- - Value: 4 - -
-
- - Value: 400 - -
- -
- Current Color: - red -
- - - -
-
-
-
- -
-
Loading
-
" -`; +exports[`if url has action_id, will render action and navigate to proper menu_id 1`] = `"
  1. Discuss
DISCUSS!!
Value: 4
Value: 400
Current Color: red
Loading
"`; -exports[`open act window action with invalid viewtype => empty widget is rendered + warning 1`] = ` -"
-
- - - - Notes - -
    - - -
- -
- -
-
- -
- -
- - Invalid View type -
-
- Cannot find view of type 'kanban' in the view registry -
-
- -
-
Loading
-
" -`; +exports[`open act window action with invalid viewtype => empty widget is rendered + warning 1`] = `"
Invalid View type
Cannot find view of type 'kanban' in the view registry
Loading
"`; -exports[`start with no action => clicks on client action => discuss is rendered 1`] = ` -"
-
- - - - Discuss - -
    - - -
- -
- -
-
-
    -
  1. - Discuss -
  2. -
-
-
- -
-
-
-
- -
-
-
- - -
-
-
-
- DISCUSS!! -
-
- - - - - - - -
- - Value: 4 - -
-
- - Value: 400 - -
- -
- Current Color: - red -
- - - -
-
-
-
- -
-
Loading
-
" -`; +exports[`start with no action => clicks on client action => discuss is rendered 1`] = `"
  1. Discuss
DISCUSS!!
Value: 4
Value: 400
Current Color: red
Loading
"`; diff --git a/src/qweb.ts b/src/qweb.ts index ec7d796d..de3d7715 100644 --- a/src/qweb.ts +++ b/src/qweb.ts @@ -32,6 +32,9 @@ const DISABLED_TAGS = [ "optgroup" ]; +const lineBreakRE = /[\r\n]/; +const whitespaceRE = /\s+/g; + //------------------------------------------------------------------------------ // Compilation Context //------------------------------------------------------------------------------ @@ -49,6 +52,7 @@ export class Context { shouldDefineOwner: boolean = false; shouldProtectContext: boolean = false; inLoop: boolean = false; + inPreTag: boolean = false; constructor() { this.rootContext = this; @@ -84,6 +88,12 @@ export class Context { return newContext; } + withInPre(): Context { + const newContext = Object.create(this); + newContext.inPreTag = true; + return newContext; + } + withCaller(node: Element): Context { const newContext = Object.create(this); newContext.caller = node; @@ -336,6 +346,12 @@ export class QWeb { if (!(node instanceof Element)) { // this is a text node, there are no directive to apply let text = node.textContent!; + if (!ctx.inPreTag) { + if (lineBreakRE.test(text) && !text.trim()) { + return; + } + text = text.replace(whitespaceRE, " "); + } if (ctx.parentNode) { ctx.addLine(`c${ctx.parentNode}.push({text: \`${text}\`});`); } else { @@ -410,6 +426,9 @@ export class QWeb { } } } + if (node.nodeName === "pre") { + ctx = ctx.withInPre(); + } this._compileChildren(node, ctx); diff --git a/tests/__snapshots__/component.test.ts.snap b/tests/__snapshots__/component.test.ts.snap index af1c3683..a96a52b6 100644 --- a/tests/__snapshots__/component.test.ts.snap +++ b/tests/__snapshots__/component.test.ts.snap @@ -8,8 +8,6 @@ exports[`composition sub widgets with some state rendered in a loop 1`] = ` let h = this.utils.h; let c1 = [], p1 = {key:1}; let vn1 = h('div', p1, c1); - c1.push({text: \` - \`}); let _2 = context['state'].numbers; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())} @@ -22,8 +20,6 @@ exports[`composition sub widgets with some state rendered in a loop 1`] = ` context.number_index = i; context.number = _3[i]; context.number_value = _4[i]; - c1.push({text: \` - \`}); //WIDGET let key8 = context['number']; let _5_index = c1.length; @@ -60,11 +56,7 @@ exports[`composition sub widgets with some state rendered in a loop 1`] = ` def6 = def6.then(()=>{if (w7.__widget__.isDestroyed) {return};let vnode;if (!w7.__widget__.vnode){vnode=w7.__widget__.pvnode} else { vnode=h(w7.__widget__.vnode.sel, {key: key8});vnode.elm=w7.el;vnode.data.hook = {insert(a){a.elm.parentNode.replaceChild(w7.el,a.elm);a.elm=w7.el;w7.__mount();},remove(){w7.destroy()}}}c1[_5_index]=vnode;}); } extra.promises.push(def6); - c1.push({text: \` - \`}); } - c1.push({text: \` - \`}); return vn1; }" `; diff --git a/tests/__snapshots__/qweb.test.ts.snap b/tests/__snapshots__/qweb.test.ts.snap index 8da0137c..32f1fab4 100644 --- a/tests/__snapshots__/qweb.test.ts.snap +++ b/tests/__snapshots__/qweb.test.ts.snap @@ -250,8 +250,6 @@ exports[`foreach does not pollute the rendering context 1`] = ` let h = this.utils.h; let c1 = [], p1 = {key:1}; let vn1 = h('div', p1, c1); - c1.push({text: \` - \`}); let _2 = [1]; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())} @@ -269,8 +267,6 @@ exports[`foreach does not pollute the rendering context 1`] = ` c1.push({text: e5}); } } - c1.push({text: \` - \`}); return vn1; }" `; @@ -282,8 +278,6 @@ exports[`foreach iterate on items (on a element node) 1`] = ` let h = this.utils.h; let c1 = [], p1 = {key:1}; let vn1 = h('div', p1, c1); - c1.push({text: \` - \`}); let _2 = [1, 2]; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())} @@ -304,8 +298,6 @@ exports[`foreach iterate on items (on a element node) 1`] = ` c5.push({text: e6}); } } - c1.push({text: \` - \`}); return vn1; }" `; @@ -317,8 +309,6 @@ exports[`foreach iterate on items 1`] = ` let h = this.utils.h; let c1 = [], p1 = {key:1}; let vn1 = h('div', p1, c1); - c1.push({text: \` - \`}); let _2 = [3, 2, 1]; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())} @@ -331,8 +321,7 @@ exports[`foreach iterate on items 1`] = ` context.item_index = i; context.item = _3[i]; context.item_value = _4[i]; - c1.push({text: \` - [\`}); + c1.push({text: \` [\`}); let e5 = context['item_index']; if (e5 || e5 === 0) { c1.push({text: e5}); @@ -347,11 +336,8 @@ exports[`foreach iterate on items 1`] = ` if (e7 || e7 === 0) { c1.push({text: e7}); } - c1.push({text: \`] - \`}); + c1.push({text: \`] \`}); } - c1.push({text: \` - \`}); return vn1; }" `; @@ -363,8 +349,6 @@ exports[`foreach iterate, dict param 1`] = ` let h = this.utils.h; let c1 = [], p1 = {key:1}; let vn1 = h('div', p1, c1); - c1.push({text: \` - \`}); let _2 = context['value']; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())} @@ -377,8 +361,7 @@ exports[`foreach iterate, dict param 1`] = ` context.item_index = i; context.item = _3[i]; context.item_value = _4[i]; - c1.push({text: \` - [\`}); + c1.push({text: \` [\`}); let e5 = context['item_index']; if (e5 || e5 === 0) { c1.push({text: e5}); @@ -398,11 +381,8 @@ exports[`foreach iterate, dict param 1`] = ` if (e8 || e8 === 0) { c1.push({text: e8}); } - c1.push({text: \`] - \`}); + c1.push({text: \`] \`}); } - c1.push({text: \` - \`}); return vn1; }" `; @@ -426,8 +406,7 @@ exports[`foreach iterate, integer param 1`] = ` context.item_index = i; context.item = _3[i]; context.item_value = _4[i]; - c1.push({text: \` - [\`}); + c1.push({text: \` [\`}); let e5 = context['item_index']; if (e5 || e5 === 0) { c1.push({text: e5}); @@ -442,8 +421,7 @@ exports[`foreach iterate, integer param 1`] = ` if (e7 || e7 === 0) { c1.push({text: e7}); } - c1.push({text: \`] - \`}); + c1.push({text: \`] \`}); } return vn1; }" @@ -456,8 +434,6 @@ exports[`foreach iterate, position 1`] = ` let h = this.utils.h; let c1 = [], p1 = {key:1}; let vn1 = h('div', p1, c1); - c1.push({text: \` - \`}); let _2 = 5; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())} @@ -470,8 +446,7 @@ exports[`foreach iterate, position 1`] = ` context.elem_index = i; context.elem = _3[i]; context.elem_value = _4[i]; - c1.push({text: \` - -\`}); + c1.push({text: \` -\`}); if (context['elem_first']) { c1.push({text: \` first\`}); } @@ -483,11 +458,8 @@ exports[`foreach iterate, position 1`] = ` if (e5 || e5 === 0) { c1.push({text: e5}); } - c1.push({text: \`) - \`}); + c1.push({text: \`) \`}); } - c1.push({text: \` - \`}); return vn1; }" `; @@ -517,8 +489,6 @@ exports[`misc global 1`] = ` let h = this.utils.h; let c1 = [], p1 = {key:1}; let vn1 = h('div', p1, c1); - c1.push({text: \` - \`}); let _2 = [4,5,6]; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())} @@ -531,8 +501,6 @@ exports[`misc global 1`] = ` context.value_index = i; context.value = _3[i]; context.value_value = _4[i]; - c1.push({text: \` - \`}); let c5 = [], p5 = {key:5,on:{}}; let vn5 = h('span', p5, c5); c1.push(vn5); @@ -540,14 +508,10 @@ exports[`misc global 1`] = ` if (e6 || e6 === 0) { c5.push({text: e6}); } - c1.push({text: \` - \`}); let _7 = 'agüero'; let c8 = [], p8 = {key:8,attrs:{\\"falló\\": _7},on:{}}; let vn8 = h('Año', p8, c8); c1.push(vn8); - c8.push({text: \` - \`}); let c9 = [], p9 = {key:9,on:{}}; let vn9 = h('span', p9, c9); c8.push(vn9); @@ -557,8 +521,6 @@ exports[`misc global 1`] = ` } else { c9.push({text: \`foo default\`}); } - c8.push({text: \` - \`}); let c11 = [], p11 = {key:11,on:{}}; let vn11 = h('span', p11, c11); c8.push(vn11); @@ -568,10 +530,6 @@ exports[`misc global 1`] = ` } else { c11.push({text: \`foo default\`}); } - c8.push({text: \` - \`}); - c8.push({text: \` - \`}); let c13 = [], p13 = {key:13,on:{}}; let vn13 = h('span', p13, c13); c8.push(vn13); @@ -581,13 +539,7 @@ exports[`misc global 1`] = ` } else { c13.push({text: \`foo default\`}); } - c8.push({text: \` - \`}); - c1.push({text: \` - \`}); } - c1.push({text: \` - \`}); let c15 = [], p15 = {key:15,on:{}}; let vn15 = h('div', p15, c15); c1.push(vn15); @@ -602,8 +554,6 @@ exports[`misc global 1`] = ` } else { c15.push({text: \`toto default\`}); } - c1.push({text: \` - \`}); return vn1; }" `; @@ -626,52 +576,36 @@ exports[`special cases for some boolean html attributes/properties various boole let h = this.utils.h; let c1 = [], p1 = {key:1}; let vn1 = h('div', p1, c1); - c1.push({text: \` - \`}); let _2 = 'checkbox'; let _3 = 'checked'; let c4 = [], p4 = {key:4,attrs:{type: _2,checked: _3},props:{checked: _3}}; let vn4 = h('input', p4, c4); c1.push(vn4); - c1.push({text: \` - \`}); let _5 = 'checked'; let c6 = [], p6 = {key:6,attrs:{checked: _5}}; let vn6 = h('input', p6, c6); c1.push(vn6); - c1.push({text: \` - \`}); let _7 = 'checked'; let c8 = [], p8 = {key:8,attrs:{checked: _7}}; let vn8 = h('div', p8, c8); c1.push(vn8); - c1.push({text: \` - \`}); let _9 = 'selected'; let c10 = [], p10 = {key:10,attrs:{selected: _9}}; let vn10 = h('div', p10, c10); c1.push(vn10); - c1.push({text: \` - \`}); let _11 = 'selected'; let _12 = '1'; let c13 = [], p13 = {key:13,attrs:{selected: _11,other: _12},props:{selected: _11}}; let vn13 = h('option', p13, c13); c1.push(vn13); - c1.push({text: \` - \`}); let _14 = 'readonly'; let c15 = [], p15 = {key:15,attrs:{readonly: _14},props:{readonly: _14}}; let vn15 = h('input', p15, c15); c1.push(vn15); - c1.push({text: \` - \`}); let _16 = 'disabled'; let c17 = [], p17 = {key:17,attrs:{disabled: _16},props:{disabled: _16}}; let vn17 = h('button', p17, c17); c1.push(vn17); - c1.push({text: \` - \`}); return vn1; }" `; @@ -751,17 +685,11 @@ exports[`t-call (template calling scoped parameters 1`] = ` let h = this.utils.h; let c1 = [], p1 = {key:1}; let vn1 = h('div', p1, c1); - c1.push({text: \` - \`}); c1.push({text: \`ok\`}); - c1.push({text: \` - \`}); let e2 = context['foo']; if (e2 || e2 === 0) { c1.push({text: e2}); } - c1.push({text: \` - \`}); return vn1; }" `; @@ -915,28 +843,20 @@ exports[`t-if boolean value condition else 1`] = ` let h = this.utils.h; let c1 = [], p1 = {key:1}; let vn1 = h('div', p1, c1); - c1.push({text: \` - \`}); let c2 = [], p2 = {key:2}; let vn2 = h('span', p2, c2); c1.push(vn2); c2.push({text: \`begin\`}); - c1.push({text: \` - \`}); if (context['condition']) { c1.push({text: \`ok\`}); } else { c1.push({text: \`ok-else\`}); } - c1.push({text: \` - \`}); let c3 = [], p3 = {key:3}; let vn3 = h('span', p3, c3); c1.push(vn3); c3.push({text: \`end\`}); - c1.push({text: \` - \`}); return vn1; }" `; @@ -1010,48 +930,30 @@ exports[`t-if can use some boolean operators in expressions 1`] = ` let h = this.utils.h; let c1 = [], p1 = {key:1}; let vn1 = h('div', p1, c1); - c1.push({text: \` - \`}); if (context['cond1'] && context['cond2']) { c1.push({text: \`and\`}); } - c1.push({text: \` - \`}); if (context['cond1'] && context['cond3']) { c1.push({text: \`nope\`}); } - c1.push({text: \` - \`}); if (context['cond1'] || context['cond3']) { c1.push({text: \`or\`}); } - c1.push({text: \` - \`}); if (context['cond3'] || context['cond4']) { c1.push({text: \`nope\`}); } - c1.push({text: \` - \`}); if (context['m'] > 3) { c1.push({text: \`mgt\`}); } - c1.push({text: \` - \`}); if (context['n'] > 3) { c1.push({text: \`ngt\`}); } - c1.push({text: \` - \`}); if (context['m'] < 3) { c1.push({text: \`mlt\`}); } - c1.push({text: \` - \`}); if (context['n'] < 3) { c1.push({text: \`nlt\`}); } - c1.push({text: \` - \`}); return vn1; }" `; @@ -1117,8 +1019,6 @@ exports[`t-on can bind handlers with loop variable as argument 1`] = ` let h = this.utils.h; let c1 = [], p1 = {key:1}; let vn1 = h('ul', p1, c1); - c1.push({text: \` - \`}); let _2 = ['someval']; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())} @@ -1140,8 +1040,6 @@ exports[`t-on can bind handlers with loop variable as argument 1`] = ` p6.on['click'] = context['activate'].bind(owner, context['action']); c6.push({text: \`link\`}); } - c1.push({text: \` - \`}); return vn1; }" `; @@ -1387,3 +1285,76 @@ exports[`t-set value priority 1`] = ` return vn1; }" `; + +exports[`whitespace handling consecutives whitespaces are condensed into a single space 1`] = ` +"function anonymous(context,extra +) { + let h = this.utils.h; + let c1 = [], p1 = {key:1}; + let vn1 = h('div', p1, c1); + c1.push({text: \` abc \`}); + return vn1; +}" +`; + +exports[`whitespace handling nothing is done in pre tags 1`] = ` +"function anonymous(context,extra +) { + let h = this.utils.h; + let c1 = [], p1 = {key:1}; + let vn1 = h('pre', p1, c1); + c1.push({text: \` \`}); + return vn1; +}" +`; + +exports[`whitespace handling nothing is done in pre tags 2`] = ` +"function anonymous(context,extra +) { + let h = this.utils.h; + let c1 = [], p1 = {key:1}; + let vn1 = h('pre', p1, c1); + c1.push({text: \` + some text + \`}); + return vn1; +}" +`; + +exports[`whitespace handling nothing is done in pre tags 3`] = ` +"function anonymous(context,extra +) { + let h = this.utils.h; + let c1 = [], p1 = {key:1}; + let vn1 = h('pre', p1, c1); + c1.push({text: \` + + \`}); + return vn1; +}" +`; + +exports[`whitespace handling white space only text nodes are condensed into a single space 1`] = ` +"function anonymous(context,extra +) { + let h = this.utils.h; + let c1 = [], p1 = {key:1}; + let vn1 = h('div', p1, c1); + c1.push({text: \` \`}); + return vn1; +}" +`; + +exports[`whitespace handling whitespace only text nodes with newlines are removed 1`] = ` +"function anonymous(context,extra +) { + let h = this.utils.h; + let c1 = [], p1 = {key:1}; + let vn1 = h('div', p1, c1); + let c2 = [], p2 = {key:2}; + let vn2 = h('span', p2, c2); + c1.push(vn2); + c2.push({text: \`abc\`}); + return vn1; +}" +`; diff --git a/tests/__snapshots__/store.test.ts.snap b/tests/__snapshots__/store.test.ts.snap index 392d8141..62eeff61 100644 --- a/tests/__snapshots__/store.test.ts.snap +++ b/tests/__snapshots__/store.test.ts.snap @@ -1,15 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`connecting a component to store connecting a component works 1`] = ` -"
- -
" -`; +exports[`connecting a component to store connecting a component works 1`] = `"
"`; -exports[`connecting a component to store connecting a component works 2`] = ` -"
- - hello - -
" -`; +exports[`connecting a component to store connecting a component works 2`] = `"
hello
"`; diff --git a/tests/qweb.test.ts b/tests/qweb.test.ts index 64041d63..60f2c793 100644 --- a/tests/qweb.test.ts +++ b/tests/qweb.test.ts @@ -921,3 +921,48 @@ describe("special cases for some boolean html attributes/properties", () => { renderToString(qweb, "test", { flag: true }); }); }); + +describe("whitespace handling", () => { + test("white space only text nodes are condensed into a single space", () => { + qweb.addTemplate("test", `
`); + const result = renderToString(qweb, "test"); + expect(result).toBe(`
`); + }); + + test("consecutives whitespaces are condensed into a single space", () => { + qweb.addTemplate("test", `
abc
`); + const result = renderToString(qweb, "test"); + expect(result).toBe(`
abc
`); + }); + + test("whitespace only text nodes with newlines are removed", () => { + qweb.addTemplate( + "test", + `
+ abc +
` + ); + const result = renderToString(qweb, "test"); + expect(result).toBe(`
abc
`); + }); + + test("nothing is done in pre tags", () => { + qweb.addTemplate("test", `
  
`); + const result = renderToString(qweb, "test"); + expect(result).toBe(`
  
`); + + const pretagtext = `
+        some text
+      
`; + qweb.addTemplate("test2", pretagtext); + const result2 = renderToString(qweb, "test2"); + expect(result2).toBe(pretagtext); + + const pretagwithonlywhitespace = `
+        
+      
`; + qweb.addTemplate("test3", pretagwithonlywhitespace); + const result3 = renderToString(qweb, "test3"); + expect(result3).toBe(pretagwithonlywhitespace); + }); +});