mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 29dc437887 | |||
| f8bb86820e | |||
| 0cde4b8737 |
@@ -140,6 +140,28 @@ class SomeComponent extends Component {
|
|||||||
The `.bind` suffix also implies `.alike`, so these props will not cause additional
|
The `.bind` suffix also implies `.alike`, so these props will not cause additional
|
||||||
renderings.
|
renderings.
|
||||||
|
|
||||||
|
## Translatable props
|
||||||
|
|
||||||
|
When you need to pass a user-facing string to a subcomponent, you likely want it
|
||||||
|
to be translated. Unfortunately, because props are arbitrary expressions, it wouldn't
|
||||||
|
be practical for Owl to find out which parts of the expression are strings and translate
|
||||||
|
them, and it also makes it difficult for tooling to extract these strings to generate
|
||||||
|
terms to translate. While you can work around this issue by doing the translation in
|
||||||
|
JavaScript, or by using `t-set` with a body (the body of `t-set` is translated),
|
||||||
|
and passing the variable as a prop, this is a sufficiently common use case that Owl
|
||||||
|
provides a suffix for this purpose: `.translate`.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<t t-name="ParentComponent">
|
||||||
|
<Child someProp.translate="some message"/>
|
||||||
|
</t>
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the content of this attribute is _NOT_ treated as a JavaScript expression:
|
||||||
|
it is treated as a string, as if it was an attribute on an HTML element, and translated
|
||||||
|
before being passed to the component. If you need to interpolate some data into the
|
||||||
|
string, you will still have to do this in JavaScript.
|
||||||
|
|
||||||
## Dynamic Props
|
## Dynamic Props
|
||||||
|
|
||||||
The `t-props` directive can be used to specify totally dynamic props:
|
The `t-props` directive can be used to specify totally dynamic props:
|
||||||
|
|||||||
@@ -201,16 +201,17 @@ use this `Notebook` component:
|
|||||||
|
|
||||||
```xml
|
```xml
|
||||||
<Notebook>
|
<Notebook>
|
||||||
<t t-set-slot="page1" title="'Page 1'">
|
<t t-set-slot="page1" title.translate="Page 1">
|
||||||
<div>this is in the page 1</div>
|
<div>this is in the page 1</div>
|
||||||
</t>
|
</t>
|
||||||
<t t-set-slot="page2" title="'Page 2'" hidden="somevalue">
|
<t t-set-slot="page2" title.translate="Page 2" hidden="somevalue">
|
||||||
<div>this is in the page 2</div>
|
<div>this is in the page 2</div>
|
||||||
</t>
|
</t>
|
||||||
</Notebook>
|
</Notebook>
|
||||||
```
|
```
|
||||||
|
|
||||||
Slot params works like normal props, so one can use the `.bind` suffix to
|
Slot params works like normal props, so one can use suffixes like `.translate`
|
||||||
|
when a prop is a user facing string and should be translated, or `.bind` to
|
||||||
bind a function if needed.
|
bind a function if needed.
|
||||||
|
|
||||||
## Slot scopes
|
## Slot scopes
|
||||||
|
|||||||
+9
-3
@@ -4594,7 +4594,12 @@ class CodeGenerator {
|
|||||||
* "onClick.bind" "onClick" "onClick: bind(ctx, ctx['onClick'])"
|
* "onClick.bind" "onClick" "onClick: bind(ctx, ctx['onClick'])"
|
||||||
*/
|
*/
|
||||||
formatProp(name, value) {
|
formatProp(name, value) {
|
||||||
|
if (name.endsWith(".translate")) {
|
||||||
|
value = toStringExpression(this.translateFn(value));
|
||||||
|
}
|
||||||
|
else {
|
||||||
value = this.captureExpression(value);
|
value = this.captureExpression(value);
|
||||||
|
}
|
||||||
if (name.includes(".")) {
|
if (name.includes(".")) {
|
||||||
let [_name, suffix] = name.split(".");
|
let [_name, suffix] = name.split(".");
|
||||||
name = _name;
|
name = _name;
|
||||||
@@ -4603,6 +4608,7 @@ class CodeGenerator {
|
|||||||
value = `(${value}).bind(this)`;
|
value = `(${value}).bind(this)`;
|
||||||
break;
|
break;
|
||||||
case "alike":
|
case "alike":
|
||||||
|
case "translate":
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new OwlError("Invalid prop suffix");
|
throw new OwlError("Invalid prop suffix");
|
||||||
@@ -5546,7 +5552,7 @@ function compile(template, options = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// do not modify manually. This file is generated by the release script.
|
// do not modify manually. This file is generated by the release script.
|
||||||
const version = "2.2.11";
|
const version = "2.3.0";
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Scheduler
|
// Scheduler
|
||||||
@@ -5975,6 +5981,6 @@ TemplateSet.prototype._compileTemplate = function _compileTemplate(name, templat
|
|||||||
export { App, Component, EventBus, OwlError, __info__, batched, blockDom, loadFile, markRaw, markup, mount, onError, onMounted, onPatched, onRendered, onWillDestroy, onWillPatch, onWillRender, onWillStart, onWillUnmount, onWillUpdateProps, reactive, status, toRaw, useChildSubEnv, useComponent, useEffect, useEnv, useExternalListener, useRef, useState, useSubEnv, validate, validateType, whenReady, xml };
|
export { App, Component, EventBus, OwlError, __info__, batched, blockDom, loadFile, markRaw, markup, mount, onError, onMounted, onPatched, onRendered, onWillDestroy, onWillPatch, onWillRender, onWillStart, onWillUnmount, onWillUpdateProps, reactive, status, toRaw, useChildSubEnv, useComponent, useEffect, useEnv, useExternalListener, useRef, useState, useSubEnv, validate, validateType, whenReady, xml };
|
||||||
|
|
||||||
|
|
||||||
__info__.date = '2024-06-17T13:31:12.099Z';
|
__info__.date = '2024-07-25T13:13:44.371Z';
|
||||||
__info__.hash = 'e7f405c';
|
__info__.hash = '0cde4b8';
|
||||||
__info__.url = 'https://github.com/odoo/owl';
|
__info__.url = 'https://github.com/odoo/owl';
|
||||||
|
|||||||
@@ -99,6 +99,11 @@ const SAMPLES = [
|
|||||||
folder: "todo_app",
|
folder: "todo_app",
|
||||||
code: ["js", "xml", "css"],
|
code: ["js", "xml", "css"],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "Tic-Tac-Toe (with reactivity)",
|
||||||
|
folder: "tic_tac_toe",
|
||||||
|
code: ["js", "xml", "css"],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "Responsive app",
|
description: "Responsive app",
|
||||||
folder: "responsive_app",
|
folder: "responsive_app",
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
.square {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #999;
|
||||||
|
float: left;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 34px;
|
||||||
|
height: 34px;
|
||||||
|
margin-right: -1px;
|
||||||
|
margin-top: -1px;
|
||||||
|
padding: 0;
|
||||||
|
text-align: center;
|
||||||
|
width: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.board-row:after {
|
||||||
|
clear: both;
|
||||||
|
content: '';
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.game {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-info {
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
// This example is an implementation of the Tic-Tac-Toe game, from
|
||||||
|
// https://react.dev/learn/tutorial-tic-tac-toe. This is an easy application to start learning owl
|
||||||
|
// with some interesting user interactions.
|
||||||
|
//
|
||||||
|
// In this implementation, we use the owl reactivity mechanism.
|
||||||
|
import { Component, useState, mount } from "@odoo/owl";
|
||||||
|
|
||||||
|
|
||||||
|
class Square extends Component {
|
||||||
|
static template = "Square";
|
||||||
|
}
|
||||||
|
|
||||||
|
class Board extends Component {
|
||||||
|
static template = "Board"
|
||||||
|
static components = { Square };
|
||||||
|
|
||||||
|
handleClick(i) {
|
||||||
|
if (this.calculateWinner(this.props.squares) || this.props.squares[i]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const nextSquares = this.props.squares.slice();
|
||||||
|
if (this.props.xIsNext) {
|
||||||
|
nextSquares[i] = 'X';
|
||||||
|
} else {
|
||||||
|
nextSquares[i] = 'O';
|
||||||
|
}
|
||||||
|
this.props.onPlay(nextSquares);
|
||||||
|
}
|
||||||
|
|
||||||
|
get status(){
|
||||||
|
const winner = this.calculateWinner(this.props.squares);
|
||||||
|
if (winner) {
|
||||||
|
return 'Winner: ' + winner;
|
||||||
|
} else {
|
||||||
|
if (Object.values(this.props.squares).filter((v) => v === null).length > 0)
|
||||||
|
return 'Next player: ' + (this.props.xIsNext ? 'X' : 'O');
|
||||||
|
else
|
||||||
|
return 'Draw';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateWinner(squares) {
|
||||||
|
const lines = [
|
||||||
|
[0, 1, 2],
|
||||||
|
[3, 4, 5],
|
||||||
|
[6, 7, 8],
|
||||||
|
[0, 3, 6],
|
||||||
|
[1, 4, 7],
|
||||||
|
[2, 5, 8],
|
||||||
|
[0, 4, 8],
|
||||||
|
[2, 4, 6],
|
||||||
|
];
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const [a, b, c] = lines[i];
|
||||||
|
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
|
||||||
|
return squares[a];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Game extends Component {
|
||||||
|
static template = "Game"
|
||||||
|
static components = { Board };
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
this.state = useState({
|
||||||
|
currentMove: 0,
|
||||||
|
history: [Array(9).fill(null)],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get currentSquares() {
|
||||||
|
return this.state.history[this.state.currentMove];
|
||||||
|
}
|
||||||
|
|
||||||
|
get xIsNext() {
|
||||||
|
return this.state.currentMove % 2 === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
jumpTo(nextMove) {
|
||||||
|
this.state.currentMove = nextMove;
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePlay(nextSquares) {
|
||||||
|
const nextHistory = [...this.state.history.slice(0, this.state.currentMove + 1), nextSquares];
|
||||||
|
this.state.history = nextHistory;
|
||||||
|
this.state.currentMove = this.state.history.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
get moves() {
|
||||||
|
return this.state.history.map((_squares, move) => {
|
||||||
|
if (move > 0) {
|
||||||
|
return {id: move, description: 'Go to move #' + move};
|
||||||
|
} else {
|
||||||
|
return {id: move, description: 'Go to game start'};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Application setup
|
||||||
|
mount(Game, document.body, { templates: TEMPLATES, dev: true});
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<templates>
|
||||||
|
<button t-name="Square" class="square" t-on-click="props.onSquareClick">
|
||||||
|
<t t-esc="props.value"/>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<t t-name="Board">
|
||||||
|
<div class="status">
|
||||||
|
<t t-esc="status"/>
|
||||||
|
</div>
|
||||||
|
<div class="board-row">
|
||||||
|
<Square value="props.squares[0]" onSquareClick="() => this.handleClick(0)" />
|
||||||
|
<Square value="props.squares[1]" onSquareClick="() => this.handleClick(1)" />
|
||||||
|
<Square value="props.squares[2]" onSquareClick="() => this.handleClick(2)" />
|
||||||
|
</div>
|
||||||
|
<div class="board-row">
|
||||||
|
<Square value="props.squares[3]" onSquareClick="() => this.handleClick(3)" />
|
||||||
|
<Square value="props.squares[4]" onSquareClick="() => this.handleClick(4)" />
|
||||||
|
<Square value="props.squares[5]" onSquareClick="() => this.handleClick(5)" />
|
||||||
|
</div>
|
||||||
|
<div class="board-row">
|
||||||
|
<Square value="props.squares[6]" onSquareClick="() => this.handleClick(6)" />
|
||||||
|
<Square value="props.squares[7]" onSquareClick="() => this.handleClick(7)" />
|
||||||
|
<Square value="props.squares[8]" onSquareClick="() => this.handleClick(8)" />
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
|
||||||
|
<div t-name="Game" class="game">
|
||||||
|
<div class="game-board">
|
||||||
|
<Board xIsNext="xIsNext" squares="currentSquares" onPlay.bind="handlePlay" />
|
||||||
|
</div>
|
||||||
|
<div class="game-info">
|
||||||
|
<ol>
|
||||||
|
<t t-foreach="moves" t-as="move" t-key="move.id">
|
||||||
|
<li>
|
||||||
|
<button t-on-click="() => this.jumpTo(move.id)">
|
||||||
|
<t t-esc="move.description"/>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</t>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</templates>
|
||||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@odoo/owl",
|
"name": "@odoo/owl",
|
||||||
"version": "2.2.11",
|
"version": "2.3.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@odoo/owl",
|
"name": "@odoo/owl",
|
||||||
"version": "2.2.11",
|
"version": "2.3.0",
|
||||||
"description": "Odoo Web Library (OWL)",
|
"description": "Odoo Web Library (OWL)",
|
||||||
"main": "dist/owl.cjs.js",
|
"main": "dist/owl.cjs.js",
|
||||||
"module": "dist/owl.es.js",
|
"module": "dist/owl.es.js",
|
||||||
|
|||||||
@@ -1136,7 +1136,11 @@ export class CodeGenerator {
|
|||||||
* "onClick.bind" "onClick" "onClick: bind(ctx, ctx['onClick'])"
|
* "onClick.bind" "onClick" "onClick: bind(ctx, ctx['onClick'])"
|
||||||
*/
|
*/
|
||||||
formatProp(name: string, value: string): string {
|
formatProp(name: string, value: string): string {
|
||||||
|
if (name.endsWith(".translate")) {
|
||||||
|
value = toStringExpression(this.translateFn(value));
|
||||||
|
} else {
|
||||||
value = this.captureExpression(value);
|
value = this.captureExpression(value);
|
||||||
|
}
|
||||||
if (name.includes(".")) {
|
if (name.includes(".")) {
|
||||||
let [_name, suffix] = name.split(".");
|
let [_name, suffix] = name.split(".");
|
||||||
name = _name;
|
name = _name;
|
||||||
@@ -1145,6 +1149,7 @@ export class CodeGenerator {
|
|||||||
value = `(${value}).bind(this)`;
|
value = `(${value}).bind(this)`;
|
||||||
break;
|
break;
|
||||||
case "alike":
|
case "alike":
|
||||||
|
case "translate":
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new OwlError("Invalid prop suffix");
|
throw new OwlError("Invalid prop suffix");
|
||||||
|
|||||||
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
// do not modify manually. This file is generated by the release script.
|
// do not modify manually. This file is generated by the release script.
|
||||||
export const version = "2.2.11";
|
export const version = "2.3.0";
|
||||||
|
|||||||
@@ -66,6 +66,29 @@ exports[`.alike suffix in a simple case 2`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`.translate props are translated 1`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
const comp1 = app.createComponent(\`Child\`, true, false, false, []);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return comp1({message: \`translated message\`}, key + \`__1\`, node, this, null);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`.translate props are translated 2`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return text(ctx['props'].message);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`basics accept ES6-like syntax for props (with getters) 1`] = `
|
exports[`basics accept ES6-like syntax for props (with getters) 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -412,6 +435,29 @@ exports[`can bind function prop with bind suffix 2`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`can use .translate suffix 1`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
const comp1 = app.createComponent(\`Child\`, true, false, false, []);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return comp1({message: \`some message\`}, key + \`__1\`, node, this, null);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`can use .translate suffix 2`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return text(ctx['props'].message);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`do not crash when binding anonymous function prop with bind suffix 1`] = `
|
exports[`do not crash when binding anonymous function prop with bind suffix 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -1,5 +1,30 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`slots .translate slot props are translated 1`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
let { capture, markRaw } = helpers;
|
||||||
|
const comp1 = app.createComponent(\`Child\`, true, true, false, []);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
const ctx1 = capture(ctx);
|
||||||
|
return comp1({slots: markRaw({'default': {message: \`translated message\`}})}, key + \`__1\`, node, this, null);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`slots .translate slot props are translated 2`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return text(ctx['props'].slots.default.message);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`slots can define a default content 1`] = `
|
exports[`slots can define a default content 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -201,6 +226,31 @@ exports[`slots can render only empty slot 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`slots can use .translate suffix on slot props 1`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
let { capture, markRaw } = helpers;
|
||||||
|
const comp1 = app.createComponent(\`Child\`, true, true, false, []);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
const ctx1 = capture(ctx);
|
||||||
|
return comp1({slots: markRaw({'default': {message: \`some message\`}})}, key + \`__1\`, node, this, null);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`slots can use .translate suffix on slot props 2`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return text(ctx['props'].slots.default.message);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`slots can use component in default-content of t-slot 1`] = `
|
exports[`slots can use component in default-content of t-slot 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -299,6 +299,34 @@ test("bound functions are considered 'alike'", async () => {
|
|||||||
expect(fixture.innerHTML).toBe("3child");
|
expect(fixture.innerHTML).toBe("3child");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("can use .translate suffix", async () => {
|
||||||
|
class Child extends Component {
|
||||||
|
static template = xml`<t t-esc="props.message"/>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`<Child message.translate="some message"/>`;
|
||||||
|
static components = { Child };
|
||||||
|
}
|
||||||
|
|
||||||
|
await mount(Parent, fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("some message");
|
||||||
|
});
|
||||||
|
|
||||||
|
test(".translate props are translated", async () => {
|
||||||
|
class Child extends Component {
|
||||||
|
static template = xml`<t t-esc="props.message"/>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`<Child message.translate="some message"/>`;
|
||||||
|
static components = { Child };
|
||||||
|
}
|
||||||
|
|
||||||
|
await mount(Parent, fixture, { translateFn: () => "translated message" });
|
||||||
|
expect(fixture.innerHTML).toBe("translated message");
|
||||||
|
});
|
||||||
|
|
||||||
test("throw if prop uses an unknown suffix", async () => {
|
test("throw if prop uses an unknown suffix", async () => {
|
||||||
class Child extends Component {
|
class Child extends Component {
|
||||||
static template = xml`<t t-esc="props.val"/>`;
|
static template = xml`<t t-esc="props.val"/>`;
|
||||||
|
|||||||
@@ -179,6 +179,34 @@ describe("slots", () => {
|
|||||||
expect(fixture.innerHTML).toBe("<span>default empty</span>");
|
expect(fixture.innerHTML).toBe("<span>default empty</span>");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("can use .translate suffix on slot props", async () => {
|
||||||
|
class Child extends Component {
|
||||||
|
static template = xml`<t t-esc="props.slots.default.message"/>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`<Child><t t-set-slot="default" message.translate="some message"/></Child>`;
|
||||||
|
static components = { Child };
|
||||||
|
}
|
||||||
|
|
||||||
|
await mount(Parent, fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("some message");
|
||||||
|
});
|
||||||
|
|
||||||
|
test(".translate slot props are translated", async () => {
|
||||||
|
class Child extends Component {
|
||||||
|
static template = xml`<t t-esc="props.slots.default.message"/>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`<Child><t t-set-slot="default" message.translate="some message"/></Child>`;
|
||||||
|
static components = { Child };
|
||||||
|
}
|
||||||
|
|
||||||
|
await mount(Parent, fixture, { translateFn: () => "translated message" });
|
||||||
|
expect(fixture.innerHTML).toBe("translated message");
|
||||||
|
});
|
||||||
|
|
||||||
test("default slot with slot scope: shorthand syntax", async () => {
|
test("default slot with slot scope: shorthand syntax", async () => {
|
||||||
let child: any;
|
let child: any;
|
||||||
class Child extends Component {
|
class Child extends Component {
|
||||||
|
|||||||
Reference in New Issue
Block a user