Odoo18-Base/addons/spreadsheet/static/tests/data_fetching/server_data.test.js
2025-01-06 10:57:38 +07:00

310 lines
12 KiB
JavaScript

import { animationFrame } from "@odoo/hoot-mock";
import { LoadingDataError } from "@spreadsheet/o_spreadsheet/errors";
import { BatchEndpoint, Request, ServerData } from "@spreadsheet/data_sources/server_data";
import { Deferred } from "@web/core/utils/concurrency";
import { describe, expect, test } from "@odoo/hoot";
import { defineSpreadsheetActions, defineSpreadsheetModels } from "../helpers/data";
describe.current.tags("headless");
defineSpreadsheetModels();
defineSpreadsheetActions();
test("simple synchronous get", async () => {
const orm = {
call: async (model, method, args) => {
expect.step(`${model}/${method}`);
return args[0];
},
};
const serverData = new ServerData(orm, {
whenDataStartLoading: () => expect.step("data-fetching-notification"),
});
expect(() => serverData.get("partner", "get_something", [5])).toThrow(LoadingDataError, {
message: "it should throw when it's not loaded",
});
expect.verifySteps(["partner/get_something", "data-fetching-notification"]);
await animationFrame();
expect(serverData.get("partner", "get_something", [5])).toBe(5);
expect.verifySteps([]);
});
test("synchronous get which returns an error", async () => {
const orm = {
call: async (model, method, args) => {
expect.step(`${model}/${method}`);
throw new Error("error while fetching data");
},
};
const serverData = new ServerData(orm, {
whenDataStartLoading: () => expect.step("data-fetching-notification"),
});
expect(() => serverData.get("partner", "get_something", [5])).toThrow(LoadingDataError, {
message: "it should throw when it's not loaded",
});
expect.verifySteps(["partner/get_something", "data-fetching-notification"]);
await animationFrame();
expect(() => serverData.get("partner", "get_something", [5])).toThrow(Error);
expect.verifySteps([]);
});
test("simple async fetch", async () => {
const orm = {
call: async (model, method, args) => {
expect.step(`${model}/${method}`);
return args[0];
},
};
const serverData = new ServerData(orm, {
whenDataStartLoading: () => expect.step("data-fetching-notification"),
});
const result = await serverData.fetch("partner", "get_something", [5]);
expect(result).toBe(5);
expect.verifySteps(["partner/get_something"]);
expect(await serverData.fetch("partner", "get_something", [5])).toBe(5);
expect.verifySteps([]);
});
test("async fetch which throws an error", async () => {
const orm = {
call: async (model, method, args) => {
expect.step(`${model}/${method}`);
throw new Error("error while fetching data");
},
};
const serverData = new ServerData(orm, {
whenDataStartLoading: () => expect.step("data-fetching-notification"),
});
expect(serverData.fetch("partner", "get_something", [5])).rejects.toThrow();
expect.verifySteps(["partner/get_something"]);
expect(serverData.fetch("partner", "get_something", [5])).rejects.toThrow();
expect.verifySteps([]);
});
test("two identical concurrent async fetch", async () => {
const orm = {
call: async (model, method, args) => {
expect.step(`${model}/${method}`);
return args[0];
},
};
const serverData = new ServerData(orm, {
whenDataStartLoading: () => expect.step("data-fetching-notification"),
});
const [result1, result2] = await Promise.all([
serverData.fetch("partner", "get_something", [5]),
serverData.fetch("partner", "get_something", [5]),
]);
// it should have fetch the data once
expect.verifySteps(["partner/get_something"]);
expect(result1).toBe(5);
expect(result2).toBe(5);
expect.verifySteps([]);
});
test("batch get with a single item", async () => {
const deferred = new Deferred();
const orm = {
call: async (model, method, args) => {
await deferred;
expect.step(`${model}/${method}`);
return args[0];
},
};
const serverData = new ServerData(orm, {
whenDataStartLoading: () => expect.step("data-fetching-notification"),
});
expect(() => serverData.batch.get("partner", "get_something_in_batch", 5)).toThrow(
LoadingDataError,
{ message: "it should throw when it's not loaded" }
);
await animationFrame(); // wait for the next tick for the batch to be called
expect.verifySteps(["data-fetching-notification"]);
deferred.resolve();
await animationFrame();
expect.verifySteps(["partner/get_something_in_batch"]);
expect(serverData.batch.get("partner", "get_something_in_batch", 5)).toBe(5);
expect.verifySteps([]);
});
test("batch get with multiple items", async () => {
const orm = {
call: async (model, method, args) => {
expect.step(`${model}/${method}`);
return args[0];
},
};
const serverData = new ServerData(orm, {
whenDataStartLoading: () => expect.step("data-fetching-notification"),
});
expect(() => serverData.batch.get("partner", "get_something_in_batch", 5)).toThrow(
LoadingDataError,
{ message: "it should throw when it's not loaded" }
);
expect(() => serverData.batch.get("partner", "get_something_in_batch", 6)).toThrow(
LoadingDataError,
{ message: "it should throw when it's not loaded" }
);
await animationFrame();
expect.verifySteps(["partner/get_something_in_batch", "data-fetching-notification"]);
expect(serverData.batch.get("partner", "get_something_in_batch", 5)).toBe(5);
expect(serverData.batch.get("partner", "get_something_in_batch", 6)).toBe(6);
expect.verifySteps([]);
});
test("batch get with one error", async () => {
const orm = {
call: async (model, method, args) => {
expect.step(`${model}/${method}`);
if (args[0].includes(5)) {
throw new Error("error while fetching data");
}
return args[0];
},
};
const serverData = new ServerData(orm, {
whenDataStartLoading: () => expect.step("data-fetching-notification"),
});
expect(() => serverData.batch.get("partner", "get_something_in_batch", 4)).toThrow(
LoadingDataError,
{ message: "it should throw when it's not loaded" }
);
expect(() => serverData.batch.get("partner", "get_something_in_batch", 5)).toThrow(
LoadingDataError,
{ message: "it should throw when it's not loaded" }
);
expect(() => serverData.batch.get("partner", "get_something_in_batch", 6)).toThrow(
LoadingDataError,
{ message: "it should throw when it's not loaded" }
);
await animationFrame();
expect.verifySteps([
// one call for the batch
"partner/get_something_in_batch",
"data-fetching-notification",
// retries one by one
"partner/get_something_in_batch",
"partner/get_something_in_batch",
"partner/get_something_in_batch",
]);
expect(serverData.batch.get("partner", "get_something_in_batch", 4)).toBe(4);
expect(() => serverData.batch.get("partner", "get_something_in_batch", 5)).toThrow(Error);
expect(serverData.batch.get("partner", "get_something_in_batch", 6)).toBe(6);
expect.verifySteps([]);
});
test("concurrently fetch then get the same request", async () => {
const orm = {
call: async (model, method, args) => {
expect.step(`${model}/${method}`);
return args[0];
},
};
const serverData = new ServerData(orm, {
whenDataStartLoading: () => expect.step("data-fetching-notification"),
});
const promise = serverData.fetch("partner", "get_something", [5]);
expect(() => serverData.get("partner", "get_something", [5])).toThrow(LoadingDataError);
// it loads the data independently
expect.verifySteps([
"partner/get_something",
"partner/get_something",
"data-fetching-notification",
]);
const result = await promise;
await animationFrame();
expect(result).toBe(5);
expect(serverData.get("partner", "get_something", [5])).toBe(5);
expect.verifySteps([]);
});
test("concurrently get then fetch the same request", async () => {
const orm = {
call: async (model, method, args) => {
expect.step(`${model}/${method}`);
return args[0];
},
};
const serverData = new ServerData(orm, {
whenDataStartLoading: () => expect.step("data-fetching-notification"),
});
expect(() => serverData.get("partner", "get_something", [5])).toThrow(LoadingDataError);
const result = await serverData.fetch("partner", "get_something", [5]);
// it should have fetch the data once
expect.verifySteps([
"partner/get_something",
"data-fetching-notification",
"partner/get_something",
]);
expect(result).toBe(5);
expect(serverData.get("partner", "get_something", [5])).toBe(5);
expect.verifySteps([]);
});
test("concurrently batch get then fetch the same request", async () => {
const orm = {
call: async (model, method, args) => {
expect.step(`${model}/${method}`);
return args[0];
},
};
const serverData = new ServerData(orm, {
whenDataStartLoading: () => expect.step("data-fetching-notification"),
});
expect(() => serverData.batch.get("partner", "get_something", 5)).toThrow(LoadingDataError);
const result = await serverData.fetch("partner", "get_something", [5]);
await animationFrame();
// it should have fetch the data once
expect.verifySteps([
"partner/get_something",
"partner/get_something",
"data-fetching-notification",
]);
expect(result).toBe(5);
expect(serverData.batch.get("partner", "get_something", 5)).toBe(5);
expect.verifySteps([]);
});
test("concurrently get and batch get the same request", async () => {
const orm = {
call: async (model, method, args) => {
expect.step(`${model}/${method}`);
return args[0];
},
};
const serverData = new ServerData(orm, {
whenDataStartLoading: () => expect.step("data-fetching-notification"),
});
expect(() => serverData.batch.get("partner", "get_something", 5)).toThrow(LoadingDataError);
expect(() => serverData.get("partner", "get_something", [5])).toThrow(LoadingDataError);
await animationFrame();
// it should have fetch the data once
expect.verifySteps(["partner/get_something", "data-fetching-notification"]);
expect(serverData.get("partner", "get_something", [5])).toBe(5);
expect(serverData.batch.get("partner", "get_something", 5)).toBe(5);
expect.verifySteps([]);
});
test("Call the correct callback after a batch result", async () => {
const orm = {
call: async (model, method, args) => {
if (args[0].includes(5)) {
throw new Error("error while fetching data");
}
return args[0];
},
};
const batchEndpoint = new BatchEndpoint(orm, "partner", "get_something", {
whenDataStartLoading: () => {},
successCallback: () => expect.step("success-callback"),
failureCallback: () => expect.step("failure-callback"),
});
const request = new Request("partner", "get_something", [4]);
const request2 = new Request("partner", "get_something", [5]);
batchEndpoint.call(request);
batchEndpoint.call(request2);
expect.verifySteps([]);
await animationFrame();
expect.verifySteps(["success-callback", "failure-callback"]);
});