Skip to content

Commit

Permalink
[IMP] Bump support for Node 18+
Browse files Browse the repository at this point in the history
This commit bump the support for Node 18+.
Some tests was adapted due to some change in Error Handling in V8 (NodeJS v20+)
  • Loading branch information
rfr-odoo committed Oct 8, 2024
1 parent 20c6cac commit be58efe
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 37 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:

strategy:
matrix:
node-version: [12.x, 14.x, 16.x]
node-version: [18.x, 20.x, 22.x]

steps:
- uses: actions/checkout@v2
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"dist"
],
"engines": {
"node": ">=12.18.3"
"node": ">=18.0.0"
},
"scripts": {
"build:bundle": "rollup -c --failAfterWarnings",
Expand Down
28 changes: 26 additions & 2 deletions tests/compiler/validation.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { TemplateSet } from "../../src/runtime/template_set";
import { renderToString, snapshotTemplate, TestContext } from "../helpers";
import {
isNode20Plus,
isNotNode20Plus,
renderToString,
snapshotTemplate,
TestContext,
testIf,
} from "../helpers";

// -----------------------------------------------------------------------------
// basic validation
Expand Down Expand Up @@ -45,11 +52,28 @@ describe("basic validation", () => {
expect(() => renderToString(template)).toThrow("Unknown QWeb directive: 't-best-beer'");
});

test("compilation error", () => {
testIf(isNotNode20Plus, "compilation error (NodeJS < 20)", () => {
const template = `<div t-att-class="a b">test</div>`;
expect(() => renderToString(template))
.toThrow(`Failed to compile anonymous template: Unexpected identifier
generated code:
function(app, bdom, helpers) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<div block-attribute-0="class">test</div>\`);
return function template(ctx, node, key = "") {
let attr1 = ctx['a']ctx['b'];
return block1([attr1]);
}
}`);
});
testIf(isNode20Plus, "compilation error (NodeJS 20+)", () => {
const template = `<div t-att-class="a b">test</div>`;
expect(() => renderToString(template))
.toThrow(`Failed to compile anonymous template: Unexpected identifier 'ctx'
generated code:
function(app, bdom, helpers) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
Expand Down
145 changes: 112 additions & 33 deletions tests/components/error_handling.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ import {
useLogLifecycle,
nextAppError,
steps,
testIf,
isNotNode20Plus,
isNode20Plus,
} from "../helpers";
import { OwlError } from "../../src/common/owl_error";

Expand Down Expand Up @@ -144,20 +147,23 @@ describe("basics", () => {
);
});

test("display a nice error if the root component template fails to compile", async () => {
// This is a special case: mount throws synchronously and we don't have any
// node which can handle the error, hence the different structure of this test
class Comp extends Component {
static template = xml`<div t-att-class="a b">test</div>`;
}
const app = new App(Comp);
let error: Error;
try {
await app.mount(fixture);
} catch (e) {
error = e as Error;
}
const expectedErrorMessage = `Failed to compile anonymous template: Unexpected identifier
testIf(
isNotNode20Plus,
"display a nice error if the root component template fails to compile",
async () => {
// This is a special case: mount throws synchronously and we don't have any
// node which can handle the error, hence the different structure of this test
class Comp extends Component {
static template = xml`<div t-att-class="a b">test</div>`;
}
const app = new App(Comp);
let error: Error;
try {
await app.mount(fixture);
} catch (e) {
error = e as Error;
}
const expectedErrorMessage = `Failed to compile anonymous template: Unexpected identifier
generated code:
function(app, bdom, helpers) {
Expand All @@ -170,19 +176,57 @@ function(app, bdom, helpers) {
return block1([attr1]);
}
}`;
expect(error!).toBeDefined();
expect(error!.message).toBe(expectedErrorMessage);
});
expect(error!).toBeDefined();
expect(error!.message).toBe(expectedErrorMessage);
}
);

testIf(
isNode20Plus,
"display a nice error if the root component template fails to compile",
async () => {
// This is a special case: mount throws synchronously and we don't have any
// node which can handle the error, hence the different structure of this test
class Comp extends Component {
static template = xml`<div t-att-class="a b">test</div>`;
}
const app = new App(Comp);
let error: Error;
try {
await app.mount(fixture);
} catch (e) {
error = e as Error;
}
const expectedErrorMessage = `Failed to compile anonymous template: Unexpected identifier 'ctx'
test("display a nice error if a non-root component template fails to compile", async () => {
class Child extends Component {
static template = xml`<div t-att-class="a b">test</div>`;
}
class Parent extends Component {
static components = { Child };
static template = xml`<Child/>`;
generated code:
function(app, bdom, helpers) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<div block-attribute-0="class">test</div>\`);
return function template(ctx, node, key = "") {
let attr1 = ctx['a']ctx['b'];
return block1([attr1]);
}
}`;
expect(error!).toBeDefined();
expect(error!.message).toBe(expectedErrorMessage);
}
const expectedErrorMessage = `Failed to compile anonymous template: Unexpected identifier
);

testIf(
isNotNode20Plus,
"display a nice error if a non-root component template fails to compile",
async () => {
class Child extends Component {
static template = xml`<div t-att-class="a b">test</div>`;
}
class Parent extends Component {
static components = { Child };
static template = xml`<Child/>`;
}
const expectedErrorMessage = `Failed to compile anonymous template: Unexpected identifier
generated code:
function(app, bdom, helpers) {
Expand All @@ -195,14 +239,49 @@ function(app, bdom, helpers) {
return block1([attr1]);
}
}`;
const app = new App(Parent as typeof Component);
let error: Error;
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow(expectedErrorMessage);
await mountProm;
expect(error!).toBeDefined();
expect(error!.message).toBe(expectedErrorMessage);
});
const app = new App(Parent as typeof Component);
let error: Error;
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow(expectedErrorMessage);
await mountProm;
expect(error!).toBeDefined();
expect(error!.message).toBe(expectedErrorMessage);
}
);

testIf(
isNode20Plus,
"display a nice error if a non-root component template fails to compile",
async () => {
class Child extends Component {
static template = xml`<div t-att-class="a b">test</div>`;
}
class Parent extends Component {
static components = { Child };
static template = xml`<Child/>`;
}
const expectedErrorMessage = `Failed to compile anonymous template: Unexpected identifier 'ctx'
generated code:
function(app, bdom, helpers) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<div block-attribute-0="class">test</div>\`);
return function template(ctx, node, key = "") {
let attr1 = ctx['a']ctx['b'];
return block1([attr1]);
}
}`;
const app = new App(Parent as typeof Component);
let error: Error;
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow(expectedErrorMessage);
await mountProm;
expect(error!).toBeDefined();
expect(error!.message).toBe(expectedErrorMessage);
}
);

test("simple catchError", async () => {
class Boom extends Component {
Expand Down
11 changes: 11 additions & 0 deletions tests/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,17 @@ export function nextAppError(app: any) {
});
}

export function testIf(predicate: Function, testName: string, testFn?: jest.ProvidesCallback) {
if (predicate()) {
test(testName, testFn);
} else {
test.skip(testName, testFn);
}
}

export const isNode20Plus = () => Number(process.versions.node.split(".")[0]) >= 20;
export const isNotNode20Plus = () => !isNode20Plus();

declare global {
namespace jest {
interface Matchers<R> {
Expand Down

0 comments on commit be58efe

Please sign in to comment.