mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c0f495661d | |||
| 0f7a8289a6 | |||
| 1c3b04f6a8 | |||
| 64db7777dd | |||
| 37313c47a3 | |||
| 3f563de9c0 | |||
| 97564a7612 | |||
| f1abf7f2ea | |||
| 8464a1b04e | |||
| c0a62dfd05 | |||
| 600f1e35d4 | |||
| b3181f119d | |||
| 1e1e05350a | |||
| b242230e20 | |||
| 9fe2da704e | |||
| 21b1661d39 | |||
| ec05b1f5e3 |
@@ -14,7 +14,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12.x, 14.x]
|
||||
node-version: [12.x, 14.x, 16.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<h1 align="center">🦉 <a href="https://odoo.github.io/owl/">OWL Framework</a> 🦉</h1>
|
||||
|
||||
[](https://www.gnu.org/licenses/lgpl-3.0)
|
||||
[](https://badge.fury.io/js/@odoo%2Fowl)
|
||||
[](https://www.npmjs.com/package/@odoo/owl)
|
||||
|
||||
_Class based components with hooks, reactive state and concurrent mode_
|
||||
|
||||
## Project Overview
|
||||
@@ -120,7 +124,7 @@ npm install @odoo/owl
|
||||
|
||||
If you want to use a simple `<script>` tag, the last release can be downloaded here:
|
||||
|
||||
- [owl-1.4.2](https://github.com/odoo/owl/releases/tag/v1.4.2)
|
||||
- [owl-1.4.6](https://github.com/odoo/owl/releases/tag/v1.4.6)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -70,7 +70,9 @@ just put the following code:
|
||||
Note that we put everything inside an immediately executed function to avoid leaking
|
||||
anything to the global scope.
|
||||
|
||||
Finally, `owl.js` should be the last version downloaded from the Owl repository (you can use `owl.min.js` if you prefer).
|
||||
Finally, `owl.js` should be the last version downloaded from the Owl repository (you can use `owl.min.js` if you prefer). Be aware that you should download the `owl.iife.js` or `owl.iife.min.js`, because these files
|
||||
are built to run directly on the browser (other files such as `owl.cjs.js` are
|
||||
built to be bundled by other tools).
|
||||
|
||||
Now, the project should be ready. Loading the `index.html` file into a browser
|
||||
should show an empty page, with the title `Owl Todo App`, and it should log a
|
||||
|
||||
+8
-7
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@odoo/owl",
|
||||
"version": "1.4.2",
|
||||
"version": "1.4.6",
|
||||
"description": "Odoo Web Library (OWL)",
|
||||
"main": "dist/owl.cjs.js",
|
||||
"browser": "dist/owl.iife.js",
|
||||
@@ -37,28 +37,29 @@
|
||||
},
|
||||
"homepage": "https://github.com/odoo/owl#readme",
|
||||
"devDependencies": {
|
||||
"@types/jest": "^23.3.14",
|
||||
"@types/jest": "^27.0.1",
|
||||
"@types/node": "^14.11.8",
|
||||
"chalk": "^3.0.0",
|
||||
"cpx": "^1.5.0",
|
||||
"current-git-branch": "^1.1.0",
|
||||
"git-rev-sync": "^1.12.0",
|
||||
"github-api": "^3.3.0",
|
||||
"jest": "^23.6.0",
|
||||
"jest-environment-jsdom": "^24.7.1",
|
||||
"jest": "^27.1.0",
|
||||
"jest-environment-jsdom": "^27.1.0",
|
||||
"live-server": "^1.2.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.0.4",
|
||||
"rollup": "^1.6.0",
|
||||
"rollup": "^2.56.3",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"rollup-plugin-typescript2": "^0.27.3",
|
||||
"rollup-plugin-typescript2": "^0.30.0",
|
||||
"sass": "^1.16.1",
|
||||
"source-map-support": "^0.5.10",
|
||||
"ts-jest": "^23.10.5",
|
||||
"ts-jest": "^27.0.5",
|
||||
"typescript": "^3.7.2",
|
||||
"uglify-es": "^3.3.9"
|
||||
},
|
||||
"jest": {
|
||||
"testEnvironment": "jsdom",
|
||||
"roots": [
|
||||
"<rootDir>/src",
|
||||
"<rootDir>/tests"
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# 🦉 OWL Roadmap 🦉
|
||||
|
||||
- Current version: 1.4.2
|
||||
- Current version: 1.4.6
|
||||
- Status: stable
|
||||
|
||||
This roadmap is only an attempt at predicting Owl's future. Everything may
|
||||
|
||||
@@ -400,6 +400,7 @@ export class Component<Props extends {} = any, T extends Env = Env> {
|
||||
if (currentFiber && !currentFiber.isRendered && !currentFiber.isCompleted) {
|
||||
return scheduler.addFiber(currentFiber.root);
|
||||
}
|
||||
|
||||
// if we aren't mounted at this point, it implies that there is a
|
||||
// currentFiber that is already rendered (isRendered is true), so we are
|
||||
// about to be mounted
|
||||
@@ -505,7 +506,6 @@ export class Component<Props extends {} = any, T extends Env = Env> {
|
||||
const __owl__ = this.__owl__;
|
||||
|
||||
__owl__.status = STATUS.MOUNTED;
|
||||
__owl__.currentFiber = null;
|
||||
this.mounted();
|
||||
if (__owl__.mountedCB) {
|
||||
__owl__.mountedCB();
|
||||
|
||||
@@ -198,6 +198,7 @@ export class Fiber {
|
||||
// build patchQueue
|
||||
const patchQueue: Fiber[] = [];
|
||||
const doWork: (Fiber) => Fiber | null = function (f) {
|
||||
f.component.__owl__.currentFiber = null;
|
||||
patchQueue.push(f);
|
||||
return f.child;
|
||||
};
|
||||
@@ -255,10 +256,6 @@ export class Fiber {
|
||||
component.__owl__.pvnode!.elm = component.__owl__.vnode!.elm;
|
||||
}
|
||||
}
|
||||
const compOwl = component.__owl__;
|
||||
if (fiber === compOwl.currentFiber) {
|
||||
compOwl.currentFiber = null;
|
||||
}
|
||||
}
|
||||
|
||||
// insert into the DOM (mount case)
|
||||
|
||||
+5
-3
@@ -1,4 +1,5 @@
|
||||
import { QWeb } from "./qweb/index";
|
||||
import { TRANSLATABLE_ATTRS } from "./qweb/qweb";
|
||||
|
||||
/**
|
||||
* This file creates and exports the OWL 'config' object, with keys:
|
||||
@@ -9,9 +10,12 @@ import { QWeb } from "./qweb/index";
|
||||
interface Config {
|
||||
mode: string;
|
||||
enableTransitions: boolean;
|
||||
translatableAttributes: string[];
|
||||
}
|
||||
|
||||
export const config = {} as Config;
|
||||
export const config = {
|
||||
translatableAttributes: TRANSLATABLE_ATTRS,
|
||||
} as Config;
|
||||
|
||||
Object.defineProperty(config, "mode", {
|
||||
get() {
|
||||
@@ -24,8 +28,6 @@ Object.defineProperty(config, "mode", {
|
||||
|
||||
This is not suitable for production use.
|
||||
See https://github.com/odoo/owl/blob/master/doc/reference/config.md#mode for more information.`);
|
||||
} else {
|
||||
console.log(`Owl is now running in 'prod' mode.`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
+32
-7
@@ -66,7 +66,7 @@ interface QWebConfig {
|
||||
// Const/global stuff/helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const TRANSLATABLE_ATTRS = ["label", "title", "placeholder", "alt"];
|
||||
export const TRANSLATABLE_ATTRS = ["label", "title", "placeholder", "alt"];
|
||||
|
||||
const lineBreakRE = /[\r\n]/;
|
||||
const whitespaceRE = /\s+/g;
|
||||
@@ -127,22 +127,36 @@ const UTILS: Utils = {
|
||||
return result;
|
||||
}
|
||||
// this is already an object, but we may need to split keys:
|
||||
// {'a': true, 'b c': true} should become {a: true, b: true, c: true}
|
||||
// {'a b': true, 'a c': false} should become {a: true, b: true, c: false}
|
||||
for (let key in expr) {
|
||||
const value = expr[key];
|
||||
const words = key.split(/\s+/);
|
||||
for (let word of words) {
|
||||
result[word] = value;
|
||||
result[word] = result[word] || value;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
/**
|
||||
* This method combines the current context with the variables defined in a
|
||||
* scope for use in a slot.
|
||||
*
|
||||
* The implementation is kind of tricky because we want to preserve the
|
||||
* prototype chain structure of the cloned result. So we need to traverse the
|
||||
* prototype chain, cloning each level respectively.
|
||||
*/
|
||||
combine(context, scope) {
|
||||
const clone = Object.create(context);
|
||||
let clone = context;
|
||||
const scopeStack = [];
|
||||
while (!isComponent(scope)) {
|
||||
Object.assign(clone, scope);
|
||||
scopeStack.push(scope);
|
||||
scope = scope.__proto__;
|
||||
}
|
||||
while (scopeStack.length) {
|
||||
let scope = scopeStack.pop();
|
||||
clone = Object.create(clone);
|
||||
Object.assign(clone, scope);
|
||||
}
|
||||
return clone;
|
||||
},
|
||||
shallowEqual,
|
||||
@@ -315,6 +329,9 @@ export class QWeb extends EventBus {
|
||||
* template, with the name given by the t-name attribute.
|
||||
*/
|
||||
addTemplates(xmlstr: string | Document) {
|
||||
if (!xmlstr) {
|
||||
return;
|
||||
}
|
||||
const doc = typeof xmlstr === "string" ? parseXML(xmlstr) : xmlstr;
|
||||
const templates = doc.getElementsByTagName("templates")[0];
|
||||
if (!templates) {
|
||||
@@ -543,7 +560,11 @@ export class QWeb extends EventBus {
|
||||
}
|
||||
|
||||
if (node.tagName !== "t" && node.hasAttribute("t-call")) {
|
||||
const tCallNode = document.createElement("t");
|
||||
const tCallNode = document.implementation.createDocument(
|
||||
"http://www.w3.org/1999/xhtml",
|
||||
"t",
|
||||
null
|
||||
).documentElement;
|
||||
tCallNode.setAttribute("t-call", node.getAttribute("t-call")!);
|
||||
node.removeAttribute("t-call");
|
||||
node.prepend(tCallNode);
|
||||
@@ -579,7 +600,11 @@ export class QWeb extends EventBus {
|
||||
throw new Error(`Unknown QWeb directive: '${attrName}'`);
|
||||
}
|
||||
if (node.tagName !== "t" && (attrName === "t-esc" || attrName === "t-raw")) {
|
||||
const tNode = document.createElement("t");
|
||||
const tNode = document.implementation.createDocument(
|
||||
"http://www.w3.org/1999/xhtml",
|
||||
"t",
|
||||
null
|
||||
).documentElement;
|
||||
tNode.setAttribute(attrName, node.getAttribute(attrName)!);
|
||||
for (let child of Array.from(node.childNodes)) {
|
||||
tNode.appendChild(child);
|
||||
|
||||
@@ -1388,6 +1388,62 @@ describe("async rendering", () => {
|
||||
expect(fixture.innerHTML).toBe("<span>4</span>");
|
||||
});
|
||||
|
||||
test("calling render in destroy", async () => {
|
||||
let a: any = null;
|
||||
let c: any = null;
|
||||
|
||||
class C extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<t t-esc="props.fromA"/>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
let flag = false;
|
||||
class B extends Component {
|
||||
static template = xml`<C fromA="props.fromA"/>`;
|
||||
static components = { C };
|
||||
|
||||
setup() {
|
||||
c = this;
|
||||
}
|
||||
|
||||
mounted() {
|
||||
if (flag) {
|
||||
this.render();
|
||||
} else {
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
willUnmount() {
|
||||
c.render();
|
||||
}
|
||||
}
|
||||
|
||||
class A extends Component {
|
||||
static template = xml`<B t-key="key" fromA="state"/>`;
|
||||
static components = { B };
|
||||
state = "a";
|
||||
key = 1;
|
||||
|
||||
setup() {
|
||||
a = this;
|
||||
}
|
||||
}
|
||||
|
||||
const parent = new A();
|
||||
await parent.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("<div>a</div>");
|
||||
|
||||
a.state = "A";
|
||||
a.key = 2;
|
||||
await a.render();
|
||||
// this nextTick is critical, otherwise jest may silently swallow errors
|
||||
await nextTick();
|
||||
|
||||
expect(fixture.innerHTML).toBe("<div>A</div>");
|
||||
});
|
||||
|
||||
test("change state and call manually render: no unnecessary rendering", async () => {
|
||||
class Widget extends Component {
|
||||
static template = xml`<div><t t-esc="state.val"/></div>`;
|
||||
|
||||
@@ -265,7 +265,8 @@ describe("class and style attributes with t-component", () => {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Cannot read property 'crash' of undefined");
|
||||
const regexp = /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
|
||||
expect(error.message).toMatch(regexp);
|
||||
expect(fixture.innerHTML).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -95,7 +95,9 @@ describe("component error handling (catchError)", () => {
|
||||
try {
|
||||
await super.render();
|
||||
} catch (e) {
|
||||
expect(e.message).toBe("Cannot read property 'this' of undefined");
|
||||
expect(e.message).toMatch(
|
||||
/Cannot read properties of undefined \(reading 'this'\)|Cannot read property 'this' of undefined/
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -359,7 +361,8 @@ describe("component error handling (catchError)", () => {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Cannot read property 'crash' of undefined");
|
||||
const regexp = /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
|
||||
expect(error.message).toMatch(regexp);
|
||||
|
||||
expect(console.error).toBeCalledTimes(0);
|
||||
console.error = consoleError;
|
||||
@@ -469,7 +472,8 @@ describe("component error handling (catchError)", () => {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Cannot read property 'crash' of undefined");
|
||||
const regexp = /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
|
||||
expect(error.message).toMatch(regexp);
|
||||
|
||||
expect(console.error).toBeCalledTimes(0);
|
||||
console.error = consoleError;
|
||||
@@ -495,7 +499,8 @@ describe("component error handling (catchError)", () => {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Cannot read property 'crash' of undefined");
|
||||
const regexp = /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
|
||||
expect(error.message).toMatch(regexp);
|
||||
|
||||
expect(console.error).toBeCalledTimes(0);
|
||||
console.error = consoleError;
|
||||
@@ -518,7 +523,8 @@ describe("component error handling (catchError)", () => {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Cannot read property 'y' of undefined");
|
||||
const regexp = /Cannot read properties of undefined \(reading 'y'\)|Cannot read property 'y' of undefined/g;
|
||||
expect(error.message).toMatch(regexp);
|
||||
});
|
||||
|
||||
test("simple catchError", async () => {
|
||||
|
||||
@@ -1303,4 +1303,71 @@ describe("t-slot directive", () => {
|
||||
document.querySelector("button").click();
|
||||
await nextTick();
|
||||
});
|
||||
|
||||
test("t-slot in recursive templates", async () => {
|
||||
QWeb.registerTemplate(
|
||||
"_test_recursive_template",
|
||||
`
|
||||
<Wrapper>
|
||||
<t t-esc="name" />
|
||||
<t t-foreach="items" t-as="item">
|
||||
|
||||
<t t-if="!item.children.length">
|
||||
<t t-esc="item.name" />
|
||||
</t>
|
||||
|
||||
<t t-else="" t-call="_test_recursive_template">
|
||||
<t t-set="name" t-value="item.name" />
|
||||
<t t-set="items" t-value="item.children" />
|
||||
</t>
|
||||
|
||||
</t>
|
||||
</Wrapper>`
|
||||
);
|
||||
|
||||
class Wrapper extends Component {
|
||||
static template = xml`
|
||||
<wrapper>
|
||||
<t t-slot="default"/>
|
||||
</wrapper>`;
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = "_test_recursive_template";
|
||||
static components = { Wrapper };
|
||||
name = "foo";
|
||||
items = [
|
||||
{
|
||||
name: "foo-0",
|
||||
children: [
|
||||
{ name: "foo-00", children: [] },
|
||||
{
|
||||
name: "foo-01",
|
||||
children: [
|
||||
{ name: "foo-010", children: [] },
|
||||
{ name: "foo-011", children: [] },
|
||||
{
|
||||
name: "foo-012",
|
||||
children: [
|
||||
{ name: "foo-0120", children: [] },
|
||||
{ name: "foo-0121", children: [] },
|
||||
{ name: "foo-0122", children: [] },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{ name: "foo-02", children: [] },
|
||||
],
|
||||
},
|
||||
{ name: "foo-1", children: [] },
|
||||
{ name: "foo-2", children: [] },
|
||||
];
|
||||
}
|
||||
|
||||
await mount(Parent, { target: fixture });
|
||||
|
||||
expect(fixture.innerHTML).toBe(
|
||||
"<wrapper>foo<wrapper>foo-0foo-00<wrapper>foo-01foo-010foo-011<wrapper>foo-012foo-0120foo-0121foo-0122</wrapper></wrapper>foo-02</wrapper>foo-1foo-2</wrapper>"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -75,8 +75,6 @@ describe("hooks", () => {
|
||||
}
|
||||
const component = new MyComponent();
|
||||
await component.mount(fixture);
|
||||
expect(component).not.toHaveProperty("mounted");
|
||||
expect(component).not.toHaveProperty("willUnmount");
|
||||
expect(fixture.innerHTML).toBe("<div>hey</div>");
|
||||
expect(steps).toEqual(["mounted"]);
|
||||
component.unmount();
|
||||
@@ -383,8 +381,6 @@ describe("hooks", () => {
|
||||
|
||||
const component = new MyComponent();
|
||||
await component.mount(fixture);
|
||||
expect(component).not.toHaveProperty("patched");
|
||||
expect(component).not.toHaveProperty("willPatch");
|
||||
expect(steps).toEqual([]);
|
||||
|
||||
expect(fixture.innerHTML).toBe("<div>hey</div>");
|
||||
@@ -630,8 +626,6 @@ describe("hooks", () => {
|
||||
|
||||
const app = new App();
|
||||
await app.mount(fixture);
|
||||
expect(app).not.toHaveProperty("willStart");
|
||||
expect(app).not.toHaveProperty("willUpdateProps");
|
||||
expect(fixture.innerHTML).toBe("<div><span>1</span></div>");
|
||||
|
||||
// NOTE: 'on2ndStart' appears first in the list even though
|
||||
|
||||
@@ -546,7 +546,8 @@ describe("Portal: Basic use and DOM placement", () => {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Cannot read property 'crash' of undefined");
|
||||
const regexp = /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
|
||||
expect(error.message).toMatch(regexp);
|
||||
});
|
||||
|
||||
test("portal manual unmount", async () => {
|
||||
|
||||
@@ -3943,6 +3943,20 @@ exports[`t-set value priority 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`translation support can add additional attributes to the list of translatable attributes 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"test\\"
|
||||
let h = this.h;
|
||||
let _1 = 'word';
|
||||
let _2 = 'mot';
|
||||
let c3 = [], p3 = {key:3,attrs:{tomato: _1,potato: _2}};
|
||||
let vn3 = h('div', p3, c3);
|
||||
c3.push({text: \`text\`});
|
||||
return vn3;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`translation support can translate node content 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
|
||||
@@ -27,3 +27,31 @@ exports[`qweb t-att t-att-class with multiple classes 2`] = `
|
||||
return vn4;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`qweb t-att t-att-class with multiple classes, some of which are duplicate 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"test\\"
|
||||
let utils = this.constructor.utils;
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let _1 = utils.toClassObj({'a b c':scope['value'],'a b d':!scope['value']});
|
||||
let c2 = [], p2 = {key:2,class:_1};
|
||||
let vn2 = h('div', p2, c2);
|
||||
return vn2;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`qweb t-att t-att-class with multiple classes, some of which are duplicate 2`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"test\\"
|
||||
let utils = this.constructor.utils;
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let _3 = utils.toClassObj({'a b c':scope['value'],'a b d':!scope['value']});
|
||||
let c4 = [], p4 = {key:4,class:_3};
|
||||
let vn4 = h('div', p4, c4);
|
||||
return vn4;
|
||||
}"
|
||||
`;
|
||||
|
||||
+15
-4
@@ -1,4 +1,5 @@
|
||||
import { QWeb } from "../../src/qweb/index";
|
||||
import { config } from "../../src/index";
|
||||
import { nextTick, normalize, renderToDOM, renderToString, trim } from "../helpers";
|
||||
import { patch } from "../../src/vdom";
|
||||
|
||||
@@ -13,6 +14,7 @@ let qweb: QWeb;
|
||||
|
||||
beforeEach(() => {
|
||||
QWeb.TEMPLATES = {};
|
||||
QWeb.nextId = 1;
|
||||
qweb = new QWeb();
|
||||
});
|
||||
|
||||
@@ -1862,8 +1864,7 @@ describe("t-ref", () => {
|
||||
|
||||
describe("loading templates", () => {
|
||||
test("can initialize qweb with a string", () => {
|
||||
const templates = `
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
const templates = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<div t-name="hey">jupiler</div>
|
||||
</templates>`;
|
||||
@@ -1872,8 +1873,7 @@ describe("loading templates", () => {
|
||||
});
|
||||
|
||||
test("can load a few templates from a xml string", () => {
|
||||
const data = `
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
const data = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="items"><li>ok</li><li>foo</li></t>
|
||||
@@ -2213,6 +2213,17 @@ describe("translation support", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("can add additional attributes to the list of translatable attributes", () => {
|
||||
const translations = {
|
||||
word: "mot",
|
||||
};
|
||||
const translateFn = (expr) => translations[expr] || expr;
|
||||
const qweb = new QWeb({ translateFn });
|
||||
config.translatableAttributes.push("potato");
|
||||
qweb.addTemplate("test", `<div tomato="word" potato="word">text</div>`);
|
||||
expect(renderToString(qweb, "test")).toBe('<div tomato="word" potato="mot">text</div>');
|
||||
});
|
||||
|
||||
test("translation is done on the trimmed text, with extra spaces readded after", () => {
|
||||
const translations = {
|
||||
word: "mot",
|
||||
|
||||
@@ -24,4 +24,13 @@ describe("qweb t-att", () => {
|
||||
'<div class="a b c"></div>'
|
||||
);
|
||||
});
|
||||
|
||||
test("t-att-class with multiple classes, some of which are duplicate", () => {
|
||||
expect(render(`<div t-att-class="{'a b c': value, 'a b d': !value}" />`, { value: true })).toBe(
|
||||
'<div class="a b c"></div>'
|
||||
);
|
||||
expect(
|
||||
render(`<div t-att-class="{'a b c': value, 'a b d': !value}" />`, { value: false })
|
||||
).toBe('<div class="a b d"></div>');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -80,7 +80,7 @@ describe("Link component", () => {
|
||||
await app.mount(fixture);
|
||||
|
||||
expect(window.location.pathname).toBe("/users");
|
||||
var evt = new MouseEvent("click", {
|
||||
var evt = new MouseEvent("contextmenu", {
|
||||
button: 1,
|
||||
});
|
||||
|
||||
|
||||
+2
-2
@@ -13,8 +13,8 @@
|
||||
// "compileOnSave": false, // Signals to the IDE to generate all files for a given tsconfig.json upon saving.
|
||||
"compilerOptions": {
|
||||
// Main options
|
||||
"target": "esnext", // Specify ECMAScript target version: 'es3' (default), 'es5', 'es2015', 'es2016', 'es2017','es2018' or 'esnext'.
|
||||
"module": "esnext", // Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'.
|
||||
"target": "es2017", // Specify ECMAScript target version: 'es3' (default), 'es5', 'es2015', 'es2016', 'es2017','es2018' or 'esnext'.
|
||||
"module": "es6", // Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'.
|
||||
// "lib": ["esnext", "dom"], // Specify library files to be included in the compilation.
|
||||
// "allowJs": false, // Allow javascript files to be compiled.
|
||||
// "checkJs": false, // Report errors in .js files.
|
||||
|
||||
Reference in New Issue
Block a user