mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-26 09:30:24 +00:00
LibTest+Spreadsheet: Add some basic spreadsheet runtime behaviour tests
As there's a somewhat active development going on, let's keep the expected behaviour under tests to make sure nothing blows up :^)
This commit is contained in:
parent
0fe97cdfe4
commit
bed129a69f
Notes:
sideshowbarker
2024-07-17 18:24:17 +09:00
Author: https://github.com/alimpfard Commit: https://github.com/SerenityOS/serenity/commit/bed129a69f Pull-request: https://github.com/SerenityOS/serenity/pull/12719 Reviewed-by: https://github.com/DavidLindbom Reviewed-by: https://github.com/linusg
13 changed files with 578 additions and 12 deletions
|
@ -6,3 +6,6 @@ NotTestsPattern=^.*(txt|frm|inc)$
|
||||||
|
|
||||||
[test-js]
|
[test-js]
|
||||||
Arguments=--show-progress=false
|
Arguments=--show-progress=false
|
||||||
|
|
||||||
|
[test-spreadsheet]
|
||||||
|
Arguments=--show-progress=false
|
||||||
|
|
|
@ -625,6 +625,19 @@ if (BUILD_LAGOM)
|
||||||
lagom_test(../../Tests/LibJS/test-invalid-unicode-js.cpp LIBS LagomJS)
|
lagom_test(../../Tests/LibJS/test-invalid-unicode-js.cpp LIBS LagomJS)
|
||||||
lagom_test(../../Tests/LibJS/test-bytecode-js.cpp LIBS LagomJS)
|
lagom_test(../../Tests/LibJS/test-bytecode-js.cpp LIBS LagomJS)
|
||||||
|
|
||||||
|
# Spreadsheet
|
||||||
|
add_executable(test-spreadsheet_lagom
|
||||||
|
../../Tests/Spreadsheet/test-spreadsheet.cpp
|
||||||
|
../../Userland/Libraries/LibTest/JavaScriptTestRunnerMain.cpp)
|
||||||
|
set_target_properties(test-spreadsheet_lagom PROPERTIES OUTPUT_NAME test-spreadsheet)
|
||||||
|
target_link_libraries(test-spreadsheet_lagom LagomCore LagomTest LagomJS)
|
||||||
|
add_test(
|
||||||
|
NAME Spreadsheet
|
||||||
|
COMMAND test-spreadsheet_lagom --show-progress=false
|
||||||
|
)
|
||||||
|
set_tests_properties(Spreadsheet PROPERTIES ENVIRONMENT SERENITY_SOURCE_DIR=${SERENITY_PROJECT_ROOT})
|
||||||
|
|
||||||
|
|
||||||
# Markdown
|
# Markdown
|
||||||
include(commonmark_spec)
|
include(commonmark_spec)
|
||||||
file(GLOB LIBMARKDOWN_TEST_SOURCES CONFIGURE_DEPENDS "../../Tests/LibMarkdown/*.cpp")
|
file(GLOB LIBMARKDOWN_TEST_SOURCES CONFIGURE_DEPENDS "../../Tests/LibMarkdown/*.cpp")
|
||||||
|
|
|
@ -165,6 +165,7 @@ cp -r "$SERENITY_SOURCE_DIR"/Userland/Libraries/LibCpp/Tests/parser mnt/home/ano
|
||||||
cp -r "$SERENITY_SOURCE_DIR"/Userland/Libraries/LibCpp/Tests/preprocessor mnt/home/anon/cpp-tests/preprocessor
|
cp -r "$SERENITY_SOURCE_DIR"/Userland/Libraries/LibCpp/Tests/preprocessor mnt/home/anon/cpp-tests/preprocessor
|
||||||
cp -r "$SERENITY_SOURCE_DIR"/Userland/Libraries/LibWasm/Tests mnt/home/anon/wasm-tests
|
cp -r "$SERENITY_SOURCE_DIR"/Userland/Libraries/LibWasm/Tests mnt/home/anon/wasm-tests
|
||||||
cp -r "$SERENITY_SOURCE_DIR"/Userland/Libraries/LibJS/Tests/test-common.js mnt/home/anon/wasm-tests
|
cp -r "$SERENITY_SOURCE_DIR"/Userland/Libraries/LibJS/Tests/test-common.js mnt/home/anon/wasm-tests
|
||||||
|
cp -r "$SERENITY_SOURCE_DIR"/Userland/Applications/Spreadsheet/Tests mnt/home/anon/spreadsheet-tests
|
||||||
|
|
||||||
if [ -n "$SERENITY_COPY_SOURCE" ] ; then
|
if [ -n "$SERENITY_COPY_SOURCE" ] ; then
|
||||||
printf "\ncopying Serenity's source... "
|
printf "\ncopying Serenity's source... "
|
||||||
|
|
|
@ -27,3 +27,4 @@ if (${SERENITY_ARCH} STREQUAL "i686")
|
||||||
endif()
|
endif()
|
||||||
add_subdirectory(LibCrypto)
|
add_subdirectory(LibCrypto)
|
||||||
add_subdirectory(LibTLS)
|
add_subdirectory(LibTLS)
|
||||||
|
add_subdirectory(Spreadsheet)
|
||||||
|
|
|
@ -76,7 +76,7 @@ TESTJS_GLOBAL_FUNCTION(mark_as_garbage, markAsGarbage)
|
||||||
return JS::js_undefined();
|
return JS::js_undefined();
|
||||||
}
|
}
|
||||||
|
|
||||||
TESTJS_RUN_FILE_FUNCTION(String const& test_file, JS::Interpreter& interpreter)
|
TESTJS_RUN_FILE_FUNCTION(String const& test_file, JS::Interpreter& interpreter, JS::ExecutionContext&)
|
||||||
{
|
{
|
||||||
if (!test262_parser_tests)
|
if (!test262_parser_tests)
|
||||||
return Test::JS::RunFileHookResult::RunAsNormal;
|
return Test::JS::RunFileHookResult::RunAsNormal;
|
||||||
|
|
3
Tests/Spreadsheet/CMakeLists.txt
Normal file
3
Tests/Spreadsheet/CMakeLists.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
serenity_testjs_test(test-spreadsheet.cpp test-spreadsheet)
|
||||||
|
install(TARGETS test-spreadsheet RUNTIME DESTINATION bin OPTIONAL)
|
||||||
|
link_with_unicode_data(test-spreadsheet)
|
44
Tests/Spreadsheet/test-spreadsheet.cpp
Normal file
44
Tests/Spreadsheet/test-spreadsheet.cpp
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <LibJS/Runtime/Map.h>
|
||||||
|
#include <LibTest/JavaScriptTestRunner.h>
|
||||||
|
|
||||||
|
TEST_ROOT("Userland/Applications/Spreadsheet/Tests");
|
||||||
|
|
||||||
|
#ifdef __serenity__
|
||||||
|
static constexpr auto s_spreadsheet_runtime_path = "/res/js/Spreadsheet/runtime.js"sv;
|
||||||
|
#else
|
||||||
|
static constexpr auto s_spreadsheet_runtime_path = "../../../../Base/res/js/Spreadsheet/runtime.js"sv;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
TESTJS_RUN_FILE_FUNCTION(String const&, JS::Interpreter& interpreter, JS::ExecutionContext& global_execution_context)
|
||||||
|
{
|
||||||
|
auto run_file = [&](StringView name) {
|
||||||
|
auto result = Test::JS::parse_script(name, interpreter.realm());
|
||||||
|
if (result.is_error()) {
|
||||||
|
warnln("Unable to parse {}", name);
|
||||||
|
warnln("{}", result.error().error.to_string());
|
||||||
|
warnln("{}", result.error().hint);
|
||||||
|
Test::cleanup_and_exit();
|
||||||
|
}
|
||||||
|
auto script = result.release_value();
|
||||||
|
|
||||||
|
interpreter.vm().push_execution_context(global_execution_context, interpreter.realm().global_object());
|
||||||
|
MUST(interpreter.run(*script));
|
||||||
|
interpreter.vm().pop_execution_context();
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __serenity__
|
||||||
|
run_file(s_spreadsheet_runtime_path);
|
||||||
|
#else
|
||||||
|
run_file(LexicalPath::join(Test::JS::g_test_root, s_spreadsheet_runtime_path).string());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
run_file("mock.test-common.js");
|
||||||
|
|
||||||
|
return Test::JS::RunFileHookResult::RunAsNormal;
|
||||||
|
}
|
97
Userland/Applications/Spreadsheet/Tests/basic.js
Normal file
97
Userland/Applications/Spreadsheet/Tests/basic.js
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
describe("Position", () => {
|
||||||
|
test("here", () => {
|
||||||
|
const workbook = createWorkbook();
|
||||||
|
const sheet = createSheet(workbook, "Sheet 1");
|
||||||
|
sheet.makeCurrent();
|
||||||
|
|
||||||
|
sheet.setCell("A", 0, "0");
|
||||||
|
sheet.focusCell("A", 0);
|
||||||
|
|
||||||
|
expect(here).toBeDefined();
|
||||||
|
let position = here();
|
||||||
|
expect(position).toBeDefined();
|
||||||
|
|
||||||
|
expect(position.column).toEqual("A");
|
||||||
|
expect(position.name).toEqual("A0");
|
||||||
|
expect(position.row).toEqual(0);
|
||||||
|
expect(position.sheet).toBe(sheet);
|
||||||
|
|
||||||
|
expect(position.contents).toEqual("0");
|
||||||
|
expect(position.value()).toEqual("0");
|
||||||
|
expect(position.toString()).toEqual("<Cell at A0>");
|
||||||
|
|
||||||
|
position.contents = "=1 + 1";
|
||||||
|
expect(position.contents).toEqual("=1 + 1");
|
||||||
|
expect(position.value()).toEqual(2);
|
||||||
|
|
||||||
|
expect(position.up().row).toEqual(0);
|
||||||
|
expect(position.down().row).toEqual(1);
|
||||||
|
expect(position.right().row).toEqual(0);
|
||||||
|
expect(position.left().row).toEqual(0);
|
||||||
|
|
||||||
|
sheet.addColumn("B");
|
||||||
|
expect(position.up().column).toEqual("A");
|
||||||
|
expect(position.down().column).toEqual("A");
|
||||||
|
expect(position.right().column).toEqual("B");
|
||||||
|
expect(position.left().column).toEqual("A");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Position.from_name", () => {
|
||||||
|
const workbook = createWorkbook();
|
||||||
|
const sheet = createSheet(workbook, "Sheet 1");
|
||||||
|
sheet.makeCurrent();
|
||||||
|
|
||||||
|
sheet.setCell("A", 0, "0");
|
||||||
|
sheet.focusCell("A", 0);
|
||||||
|
|
||||||
|
expect(Position.from_name).toBeDefined();
|
||||||
|
let position = Position.from_name("A0");
|
||||||
|
expect(position).toBeInstanceOf(Position);
|
||||||
|
|
||||||
|
position = Position.from_name("A123");
|
||||||
|
expect(position).toBeInstanceOf(Position);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Range", () => {
|
||||||
|
test("simple", () => {
|
||||||
|
const workbook = createWorkbook();
|
||||||
|
const sheet = createSheet(workbook, "Sheet 1");
|
||||||
|
sheet.makeCurrent();
|
||||||
|
|
||||||
|
sheet.setCell("A", 0, "0");
|
||||||
|
sheet.setCell("A", 10, "0");
|
||||||
|
sheet.setCell("B", 1, "0");
|
||||||
|
sheet.focusCell("A", 0);
|
||||||
|
|
||||||
|
expect(R).toBeDefined();
|
||||||
|
let cellsVisited = 0;
|
||||||
|
R`A0:A10`.forEach(name => {
|
||||||
|
++cellsVisited;
|
||||||
|
});
|
||||||
|
expect(cellsVisited).toEqual(11);
|
||||||
|
|
||||||
|
cellsVisited = 0;
|
||||||
|
R`A0:A10:1:2`.forEach(name => {
|
||||||
|
++cellsVisited;
|
||||||
|
});
|
||||||
|
expect(cellsVisited).toEqual(6);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Ranges", () => {
|
||||||
|
const workbook = createWorkbook();
|
||||||
|
const sheet = createSheet(workbook, "Sheet 1");
|
||||||
|
sheet.makeCurrent();
|
||||||
|
|
||||||
|
sheet.setCell("A", 0, "0");
|
||||||
|
sheet.setCell("A", 10, "0");
|
||||||
|
sheet.setCell("B", 1, "0");
|
||||||
|
sheet.focusCell("A", 0);
|
||||||
|
|
||||||
|
let cellsVisited = 0;
|
||||||
|
R`A0:A5`.union(R`A6:A10`).forEach(name => {
|
||||||
|
++cellsVisited;
|
||||||
|
});
|
||||||
|
expect(cellsVisited).toEqual(11);
|
||||||
|
});
|
||||||
|
});
|
166
Userland/Applications/Spreadsheet/Tests/free-functions.js
Normal file
166
Userland/Applications/Spreadsheet/Tests/free-functions.js
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
describe("Basic functions", () => {
|
||||||
|
const workbook = createWorkbook();
|
||||||
|
const sheet = createSheet(workbook, "Sheet 1");
|
||||||
|
sheet.makeCurrent();
|
||||||
|
|
||||||
|
sheet.setCell("A", 0, "0");
|
||||||
|
sheet.setCell("A", 1, "1");
|
||||||
|
sheet.setCell("A", 2, "2");
|
||||||
|
|
||||||
|
test("select", () => {
|
||||||
|
expect(select).toBeDefined();
|
||||||
|
expect(select(true, 1, 2)).toBe(1);
|
||||||
|
expect(select(false, 1, 2)).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("choose", () => {
|
||||||
|
expect(choose).toBeDefined();
|
||||||
|
expect(choose(0, 1, 2, 3)).toBe(1);
|
||||||
|
expect(choose(1, 1, 2, 3)).toBe(2);
|
||||||
|
expect(choose(3, 1, 2, 3)).toBeUndefined();
|
||||||
|
expect(choose(-1, 1, 2, 3)).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("now", () => {
|
||||||
|
expect(now).toBeDefined();
|
||||||
|
expect(now()).toBeInstanceOf(Date);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("randRange", () => {
|
||||||
|
expect(randRange).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("integer", () => {
|
||||||
|
expect(integer).toBeDefined();
|
||||||
|
expect(integer("0")).toEqual(0);
|
||||||
|
expect(integer("32")).toEqual(32);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("sheet", () => {
|
||||||
|
expect(globalThis.sheet).toBeDefined();
|
||||||
|
expect(globalThis.sheet("Sheet 1")).toBe(sheet);
|
||||||
|
expect(globalThis.sheet("Not a sheet")).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("reduce", () => {
|
||||||
|
expect(reduce).toBeDefined();
|
||||||
|
expect(reduce(acc => acc + 1, 0, [1, 2, 3, 4])).toEqual(4);
|
||||||
|
expect(reduce(acc => acc + 1, 0, [])).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("numericReduce", () => {
|
||||||
|
expect(numericReduce).toBeDefined();
|
||||||
|
expect(numericReduce(acc => acc + 1, 0, [1, 2, 3, 4])).toEqual(4);
|
||||||
|
expect(numericReduce(acc => acc + 1, 0, [])).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("numericResolve", () => {
|
||||||
|
expect(numericResolve).toBeDefined();
|
||||||
|
expect(numericResolve(["A0", "A1", "A2"])).toEqual([0, 1, 2]);
|
||||||
|
expect(numericResolve([])).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("resolve", () => {
|
||||||
|
expect(resolve).toBeDefined();
|
||||||
|
expect(resolve(["A0", "A1", "A2"])).toEqual(["0", "1", "2"]);
|
||||||
|
expect(resolve([])).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Statistics", () => {
|
||||||
|
const workbook = createWorkbook();
|
||||||
|
const sheet = createSheet(workbook, "Sheet 1");
|
||||||
|
sheet.makeCurrent();
|
||||||
|
|
||||||
|
for (let i = 0; i < 10; ++i) sheet.setCell("A", i, `${i}`);
|
||||||
|
|
||||||
|
test("sum", () => {
|
||||||
|
expect(sum).toBeDefined();
|
||||||
|
expect(sum(R`A0:A9`)).toEqual(45);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("sumIf", () => {
|
||||||
|
expect(sumIf).toBeDefined();
|
||||||
|
expect(sumIf(x => !Number.isNaN(x), R`A0:A10`)).toEqual(45);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("count", () => {
|
||||||
|
expect(count).toBeDefined();
|
||||||
|
expect(count(R`A0:A9`)).toEqual(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("countIf", () => {
|
||||||
|
expect(countIf).toBeDefined();
|
||||||
|
expect(countIf(x => x, R`A0:A10`)).toEqual(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("average", () => {
|
||||||
|
expect(average).toBeDefined();
|
||||||
|
expect(average(R`A0:A9`)).toEqual(4.5);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("averageIf", () => {
|
||||||
|
expect(averageIf).toBeDefined();
|
||||||
|
expect(averageIf(x => !Number.isNaN(x), R`A0:A10`)).toEqual(4.5);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("median", () => {
|
||||||
|
expect(median).toBeDefined();
|
||||||
|
expect(median(R`A0:A9`)).toEqual(4.5);
|
||||||
|
expect(median(R`A0:A2`)).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("variance", () => {
|
||||||
|
expect(variance).toBeDefined();
|
||||||
|
expect(variance(R`A0:A0`)).toEqual(0);
|
||||||
|
expect(variance(R`A0:A9`)).toEqual(82.5);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("mode", () => {
|
||||||
|
expect(mode).toBeDefined();
|
||||||
|
expect(mode(R`A0:A0`.union(R`A0:A0`).union(R`A1:A9`))).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("stddev", () => {
|
||||||
|
expect(stddev).toBeDefined();
|
||||||
|
expect(stddev(R`A0:A0`)).toEqual(0);
|
||||||
|
expect(stddev(R`A0:A9`)).toEqual(Math.sqrt(82.5));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Lookup", () => {
|
||||||
|
const workbook = createWorkbook();
|
||||||
|
const sheet = createSheet(workbook, "Sheet 1");
|
||||||
|
sheet.makeCurrent();
|
||||||
|
|
||||||
|
for (let i = 0; i < 10; ++i) {
|
||||||
|
sheet.setCell("A", i, `${i}`);
|
||||||
|
sheet.setCell("B", i, `B${i}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
sheet.focusCell("A", 0);
|
||||||
|
|
||||||
|
test("row", () => {
|
||||||
|
expect(row()).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("column", () => {
|
||||||
|
expect(column()).toEqual("A");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("lookup", () => {
|
||||||
|
expect(lookup).toBeDefined();
|
||||||
|
// Note: String ordering.
|
||||||
|
expect(lookup("2", R`A0:A9`, R`B0:B9`)).toEqual("B2");
|
||||||
|
expect(lookup("20", R`A0:A9`, R`B0:B9`)).toBeUndefined();
|
||||||
|
expect(lookup("80", R`A0:A9`, R`B0:B9`, undefined, "nextlargest")).toEqual("B9");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("reflookup", () => {
|
||||||
|
expect(reflookup).toBeDefined();
|
||||||
|
// Note: String ordering.
|
||||||
|
expect(reflookup("2", R`A0:A9`, R`B0:B9`).name).toEqual("B2");
|
||||||
|
expect(reflookup("20", R`A0:A9`, R`B0:B9`)).toEqual(here());
|
||||||
|
expect(reflookup("80", R`A0:A9`, R`B0:B9`, undefined, "nextlargest").name).toEqual("B9");
|
||||||
|
});
|
||||||
|
});
|
192
Userland/Applications/Spreadsheet/Tests/mock.test-common.js
Normal file
192
Userland/Applications/Spreadsheet/Tests/mock.test-common.js
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
var thisSheet;
|
||||||
|
var workbook;
|
||||||
|
|
||||||
|
var createWorkbook = () => {
|
||||||
|
return {
|
||||||
|
__sheets: new Map(),
|
||||||
|
sheet(nameOrIndex) {
|
||||||
|
if (typeof nameOrIndex !== "number") return this.__sheets.get(nameOrIndex);
|
||||||
|
for (const entry of this.__sheets) {
|
||||||
|
if (nameOrIndex === 0) return entry[1];
|
||||||
|
nameOrIndex--;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function toBijectiveBase(number) {
|
||||||
|
const alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
|
number += 1;
|
||||||
|
let c = 0;
|
||||||
|
let x = 1;
|
||||||
|
while (number >= x) {
|
||||||
|
++c;
|
||||||
|
number -= x;
|
||||||
|
x *= 26;
|
||||||
|
}
|
||||||
|
|
||||||
|
let s = "";
|
||||||
|
for (let i = 0; i < c; i++) {
|
||||||
|
s = alpha.charAt(number % 26) + s;
|
||||||
|
number = Math.floor(number / 26);
|
||||||
|
}
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
function __evaluate(expression, that) {
|
||||||
|
const object = Object.create(null);
|
||||||
|
for (const entry of that.__cells) {
|
||||||
|
const cell = JSON.parse(entry[0]);
|
||||||
|
object[`${cell[0]}${cell[1]}`] = entry[1][1];
|
||||||
|
}
|
||||||
|
|
||||||
|
const sheetObject = that;
|
||||||
|
let __value;
|
||||||
|
|
||||||
|
// Warning: Dragons and fire ahead.
|
||||||
|
with (that.__workbook) {
|
||||||
|
with (object) {
|
||||||
|
with (sheetObject) {
|
||||||
|
__value = eval(expression);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return __value;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Sheet {
|
||||||
|
constructor(workbook) {
|
||||||
|
this.__cells = new Map();
|
||||||
|
this.__columns = new Set();
|
||||||
|
this.__workbook = workbook;
|
||||||
|
this.__currentCellPosition = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
get_real_cell_contents(name) {
|
||||||
|
const cell = this.parse_cell_name(name);
|
||||||
|
if (cell === undefined) throw new TypeError("Invalid cell name");
|
||||||
|
return this.getCell(cell.column, cell.row)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
set_real_cell_contents(name, value) {
|
||||||
|
const cell = this.parse_cell_name(name);
|
||||||
|
if (cell === undefined) throw new TypeError("Invalid cell name");
|
||||||
|
|
||||||
|
this.setCell(cell.column, cell.row, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_cell_name(name) {
|
||||||
|
const match = /^([a-zA-Z]+)(\d+)$/.exec(name);
|
||||||
|
if (!Array.isArray(match)) return undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
column: match[1],
|
||||||
|
row: +match[2],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
current_cell_position() {
|
||||||
|
return this.__currentCellPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
column_index(name) {
|
||||||
|
let i = 0;
|
||||||
|
for (const column of this.__columns) {
|
||||||
|
if (column === name) return i;
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
column_arithmetic(name, offset) {
|
||||||
|
if (offset < 0) {
|
||||||
|
const columns = this.getColumns();
|
||||||
|
let index = columns.indexOf(name);
|
||||||
|
if (index === -1) throw new TypeError(`${name} is not a valid column name`);
|
||||||
|
|
||||||
|
index += offset;
|
||||||
|
if (index < 0) return columns[0];
|
||||||
|
return columns[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
let found = false;
|
||||||
|
for (const column of this.__columns) {
|
||||||
|
if (!found) found = column === name;
|
||||||
|
if (found) {
|
||||||
|
if (offset === 0) return column;
|
||||||
|
offset--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) throw new TypeError(`${name} is not a valid column name`);
|
||||||
|
|
||||||
|
let newName;
|
||||||
|
for (let i = 0; i < offset; ++i) {
|
||||||
|
newName = toBijectiveBase(this.__columns.size);
|
||||||
|
this.addColumn(newName);
|
||||||
|
}
|
||||||
|
return newName;
|
||||||
|
}
|
||||||
|
|
||||||
|
get_column_bound(name) {
|
||||||
|
let bound = 0;
|
||||||
|
for (const entry of this.__cells) {
|
||||||
|
const [column, row] = JSON.parse(entry[0]);
|
||||||
|
if (column !== name) continue;
|
||||||
|
bound = Math.max(bound, row);
|
||||||
|
}
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluate(currentColumn, currentRow, expression) {
|
||||||
|
const currentCellSave = this.__currentCellPosition;
|
||||||
|
this.__currentCellPosition = { column: currentColumn, row: currentRow };
|
||||||
|
try {
|
||||||
|
return __evaluate(expression, this);
|
||||||
|
} finally {
|
||||||
|
this.__currentCellPosition = currentCellSave;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addColumn(name) {
|
||||||
|
this.__columns.add(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
getColumns() {
|
||||||
|
return Array.from(this.__columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
setCell(column, row, source, value = undefined) {
|
||||||
|
this.addColumn(column);
|
||||||
|
source = `${source}`;
|
||||||
|
if (value === undefined) {
|
||||||
|
value = source;
|
||||||
|
if (value[0] === "=") value = this.evaluate(column, row, value.substr(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.__cells.set(JSON.stringify([column, row]), [source, value]);
|
||||||
|
this[`${column}${row}`] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCell(column, row) {
|
||||||
|
const data = this.__cells.get(JSON.stringify([column, row]));
|
||||||
|
if (data === undefined) return undefined;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
focusCell(column, row) {
|
||||||
|
this.__currentCellPosition = { column, row };
|
||||||
|
}
|
||||||
|
|
||||||
|
makeCurrent() {
|
||||||
|
thisSheet = this;
|
||||||
|
workbook = this.__workbook;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var createSheet = (workbook, name) => {
|
||||||
|
const sheet = new Sheet(workbook);
|
||||||
|
workbook.__sheets.set(name, sheet);
|
||||||
|
return sheet;
|
||||||
|
};
|
46
Userland/Applications/Spreadsheet/Tests/test-harness.js
Normal file
46
Userland/Applications/Spreadsheet/Tests/test-harness.js
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
describe("Harness-defined functions", () => {
|
||||||
|
test("createWorkbook", () => {
|
||||||
|
expect(createWorkbook).toBeDefined();
|
||||||
|
const workbook = createWorkbook();
|
||||||
|
expect(workbook).toBeDefined();
|
||||||
|
expect(workbook.sheet).toBeDefined();
|
||||||
|
});
|
||||||
|
test("createSheet", () => {
|
||||||
|
const workbook = createWorkbook();
|
||||||
|
const sheet = createSheet(workbook, "foo");
|
||||||
|
expect(sheet).toBeDefined();
|
||||||
|
expect(sheet.get_real_cell_contents).toBeDefined();
|
||||||
|
expect(sheet.set_real_cell_contents).toBeDefined();
|
||||||
|
expect(sheet.parse_cell_name).toBeDefined();
|
||||||
|
expect(sheet.current_cell_position).toBeDefined();
|
||||||
|
expect(sheet.column_index).toBeDefined();
|
||||||
|
expect(sheet.column_arithmetic).toBeDefined();
|
||||||
|
expect(sheet.get_column_bound).toBeDefined();
|
||||||
|
});
|
||||||
|
test("Sheet mock behavior", () => {
|
||||||
|
const workbook = createWorkbook();
|
||||||
|
const sheet = createSheet(workbook, "foo");
|
||||||
|
sheet.setCell("A", 0, "10");
|
||||||
|
expect(sheet.getCell("A", 0)).toEqual(["10", "10"]);
|
||||||
|
|
||||||
|
sheet.setCell("A", 0, "=10");
|
||||||
|
expect(sheet.getCell("A", 0)).toEqual(["=10", 10]);
|
||||||
|
|
||||||
|
expect(sheet.getColumns()).toEqual(["A"]);
|
||||||
|
});
|
||||||
|
test("Workbook mock behavior", () => {
|
||||||
|
const workbook = createWorkbook();
|
||||||
|
const sheet = createSheet(workbook, "foo");
|
||||||
|
expect(workbook.sheet("foo")).toBe(sheet);
|
||||||
|
expect(workbook.sheet(0)).toBe(sheet);
|
||||||
|
expect(workbook.sheet(1)).toBeUndefined();
|
||||||
|
expect(workbook.sheet("bar")).toBeUndefined();
|
||||||
|
});
|
||||||
|
test("Referencing cells", () => {
|
||||||
|
const workbook = createWorkbook();
|
||||||
|
const sheet = createSheet(workbook, "foo");
|
||||||
|
sheet.setCell("A", 0, "42");
|
||||||
|
sheet.setCell("A", 1, "=A0");
|
||||||
|
expect(sheet.getCell("A", 1)).toEqual(["=A0", "42"]);
|
||||||
|
});
|
||||||
|
});
|
|
@ -95,7 +95,7 @@
|
||||||
{ \
|
{ \
|
||||||
::Test::JS::g_run_file = hook; \
|
::Test::JS::g_run_file = hook; \
|
||||||
} \
|
} \
|
||||||
static ::Test::JS::IntermediateRunFileResult hook(const String&, JS::Interpreter&); \
|
static ::Test::JS::IntermediateRunFileResult hook(const String&, JS::Interpreter&, JS::ExecutionContext&); \
|
||||||
} __testjs_common_run_file {}; \
|
} __testjs_common_run_file {}; \
|
||||||
::Test::JS::IntermediateRunFileResult __TestJS_run_file::hook(__VA_ARGS__)
|
::Test::JS::IntermediateRunFileResult __TestJS_run_file::hook(__VA_ARGS__)
|
||||||
|
|
||||||
|
@ -163,7 +163,7 @@ enum class RunFileHookResult {
|
||||||
};
|
};
|
||||||
|
|
||||||
using IntermediateRunFileResult = AK::Result<JSFileResult, RunFileHookResult>;
|
using IntermediateRunFileResult = AK::Result<JSFileResult, RunFileHookResult>;
|
||||||
extern IntermediateRunFileResult (*g_run_file)(const String&, JS::Interpreter&);
|
extern IntermediateRunFileResult (*g_run_file)(const String&, JS::Interpreter&, JS::ExecutionContext&);
|
||||||
|
|
||||||
class TestRunner : public ::Test::TestRunner {
|
class TestRunner : public ::Test::TestRunner {
|
||||||
public:
|
public:
|
||||||
|
@ -300,7 +300,7 @@ inline JSFileResult TestRunner::run_file_test(const String& test_path)
|
||||||
interpreter->heap().set_should_collect_on_every_allocation(g_collect_on_every_allocation);
|
interpreter->heap().set_should_collect_on_every_allocation(g_collect_on_every_allocation);
|
||||||
|
|
||||||
if (g_run_file) {
|
if (g_run_file) {
|
||||||
auto result = g_run_file(test_path, *interpreter);
|
auto result = g_run_file(test_path, *interpreter, global_execution_context);
|
||||||
if (result.is_error() && result.error() == RunFileHookResult::SkipFile) {
|
if (result.is_error() && result.error() == RunFileHookResult::SkipFile) {
|
||||||
return {
|
return {
|
||||||
test_path,
|
test_path,
|
||||||
|
|
|
@ -25,7 +25,7 @@ HashMap<String, FunctionWithLength> s_exposed_global_functions;
|
||||||
Function<void()> g_main_hook;
|
Function<void()> g_main_hook;
|
||||||
Function<NonnullOwnPtr<JS::Interpreter>()> g_create_interpreter_hook;
|
Function<NonnullOwnPtr<JS::Interpreter>()> g_create_interpreter_hook;
|
||||||
HashMap<bool*, Tuple<String, String, char>> g_extra_args;
|
HashMap<bool*, Tuple<String, String, char>> g_extra_args;
|
||||||
IntermediateRunFileResult (*g_run_file)(const String&, JS::Interpreter&) = nullptr;
|
IntermediateRunFileResult (*g_run_file)(const String&, JS::Interpreter&, JS::ExecutionContext&) = nullptr;
|
||||||
String g_test_root;
|
String g_test_root;
|
||||||
int g_test_argc;
|
int g_test_argc;
|
||||||
char** g_test_argv;
|
char** g_test_argv;
|
||||||
|
|
Loading…
Reference in a new issue