@itwin/core-backend
Version:
iTwin.js backend components
510 lines • 27.2 kB
JavaScript
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
if (value !== null && value !== void 0) {
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
var dispose, inner;
if (async) {
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
dispose = value[Symbol.asyncDispose];
}
if (dispose === void 0) {
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
dispose = value[Symbol.dispose];
if (async) inner = dispose;
}
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
env.stack.push({ value: value, dispose: dispose, async: async });
}
else if (async) {
env.stack.push({ async: true });
}
return value;
};
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
return function (env) {
function fail(e) {
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
env.hasError = true;
}
var r, s = 0;
function next() {
while (r = env.stack.pop()) {
try {
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
if (r.dispose) {
var result = r.dispose.call(r.value);
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
}
else s |= 1;
}
catch (e) {
fail(e);
}
}
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
if (env.hasError) throw env.error;
}
return next();
};
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
});
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { assert } from "chai";
import * as path from "path";
import { DbResult } from "@itwin/core-bentley";
import { Range3d } from "@itwin/core-geometry";
import { ECDb, ECDbOpenMode, SqliteValueType } from "../../core-backend";
import { KnownTestLocations } from "../KnownTestLocations";
import { ECDbTestHelper } from "./ECDbTestHelper";
import { SequentialLogMatcher } from "../SequentialLogMatcher";
describe("SqliteStatement", () => {
const outDir = KnownTestLocations.outputDir;
const stringVal = "Hello world";
const intVal = 0;
const doubleVal = -2.5;
const blobVal = new Uint8Array(new Range3d(1.2, 2.3, 3.4, 4.5, 5.6, 6.7).toFloat64Array().buffer);
it("create table, insert, select with ecdb", () => {
const env_1 = { stack: [], error: void 0, hasError: false };
try {
const ecdb = __addDisposableResource(env_1, ECDbTestHelper.createECDb(outDir, "sqlitestatement.ecdb"), false);
assert.isTrue(ecdb.isOpen);
ecdb.withSqliteStatement("CREATE TABLE MyTable(id INTEGER PRIMARY KEY, stringcol TEXT, intcol INTEGER, doublecol REAL, blobcol)", (stmt) => {
assert.isFalse(stmt.isReadonly);
assert.equal(stmt.step(), DbResult.BE_SQLITE_DONE);
});
const stmt1 = "INSERT INTO MyTable(stringcol,intcol,doublecol,blobcol) VALUES(?,?,?,?)";
ecdb.withPreparedSqliteStatement(stmt1, (stmt) => {
assert.isFalse(stmt.isReadonly);
stmt.bindValue(1, stringVal);
stmt.bindValue(2, intVal);
stmt.bindValue(3, doubleVal);
stmt.bindValue(4, blobVal);
assert.equal(stmt.step(), DbResult.BE_SQLITE_DONE);
});
ecdb.withPreparedSqliteStatement(stmt1, (stmt) => {
stmt.maybeBindString(1, stringVal);
stmt.maybeBindInteger(2, intVal);
stmt.maybeBindDouble(3, doubleVal);
stmt.maybeBindBlob(4, blobVal);
assert.equal(stmt.step(), DbResult.BE_SQLITE_DONE);
});
ecdb.withPreparedSqliteStatement(stmt1, (stmt) => {
assert.isFalse(stmt.isReadonly);
stmt.bindValues([stringVal, intVal, doubleVal, blobVal]);
assert.equal(stmt.step(), DbResult.BE_SQLITE_DONE);
});
const stmt2 = "INSERT INTO MyTable(stringcol,intcol,doublecol,blobcol) VALUES(:string,:int,:double,:blob)";
ecdb.withPreparedSqliteStatement(stmt2, (stmt) => {
assert.isFalse(stmt.isReadonly);
stmt.bindValue(":string", stringVal);
stmt.bindValue(":int", intVal);
stmt.bindValue(":double", doubleVal);
stmt.bindValue(":blob", blobVal);
assert.equal(stmt.step(), DbResult.BE_SQLITE_DONE);
});
ecdb.withPreparedSqliteStatement(stmt2, (stmt) => {
assert.isFalse(stmt.isReadonly);
stmt.bindValues({ ":string": stringVal, ":int": intVal, ":double": doubleVal, ":blob": blobVal });
assert.equal(stmt.step(), DbResult.BE_SQLITE_DONE);
});
ecdb.saveChanges();
ecdb.withPreparedSqliteStatement("SELECT id,stringcol,intcol,doublecol,blobcol FROM MyTable", (stmt) => {
assert.isTrue(stmt.isReadonly);
let rowCount = 0;
while (stmt.step() === DbResult.BE_SQLITE_ROW) {
rowCount++;
assert.equal(stmt.getColumnCount(), 5);
const idSqlVal = stmt.getValue(0);
assert.isFalse(idSqlVal.isNull);
assert.equal(idSqlVal.type, SqliteValueType.Integer);
assert.isDefined(idSqlVal.value);
assert.equal(idSqlVal.value, rowCount);
assert.equal(idSqlVal.getInteger(), rowCount);
assert.equal(idSqlVal.columnName, "id");
const stringSqlVal = stmt.getValue(1);
assert.isFalse(stringSqlVal.isNull);
assert.equal(stringSqlVal.type, SqliteValueType.String);
assert.isDefined(stringSqlVal.value);
assert.equal(stringSqlVal.value, stringVal);
assert.equal(stringSqlVal.getString(), stringVal);
assert.equal(stringSqlVal.columnName, "stringcol");
const intSqlVal = stmt.getValue(2);
assert.isFalse(intSqlVal.isNull);
assert.equal(intSqlVal.type, SqliteValueType.Integer);
assert.isDefined(intSqlVal.value);
assert.equal(intSqlVal.value, intVal);
assert.equal(intSqlVal.getInteger(), intVal);
assert.equal(intSqlVal.columnName, "intcol");
const doubleSqlVal = stmt.getValue(3);
assert.isFalse(doubleSqlVal.isNull);
assert.equal(doubleSqlVal.type, SqliteValueType.Double);
assert.isDefined(doubleSqlVal.value);
assert.equal(doubleSqlVal.value, doubleVal);
assert.equal(doubleSqlVal.getDouble(), doubleVal);
assert.equal(doubleSqlVal.columnName, "doublecol");
const blobSqlVal = stmt.getValue(4);
assert.isFalse(blobSqlVal.isNull);
assert.equal(blobSqlVal.type, SqliteValueType.Blob);
assert.isDefined(blobSqlVal.value);
assert.equal(blobSqlVal.value.byteLength, blobVal.byteLength);
assert.equal(blobSqlVal.getBlob().byteLength, blobVal.byteLength);
assert.equal(blobSqlVal.columnName, "blobcol");
}
assert.equal(rowCount, 5);
});
ecdb.withPreparedSqliteStatement("SELECT id,stringcol,intcol,doublecol,blobcol FROM MyTable", (stmt) => {
assert.isTrue(stmt.isReadonly);
let rowCount = 0;
while (stmt.step() === DbResult.BE_SQLITE_ROW) {
rowCount++;
const row = stmt.getRow();
assert.isDefined(row.id);
assert.equal(row.id, rowCount);
assert.isDefined(row.stringcol);
assert.equal(row.stringcol, stringVal);
assert.isDefined(row.intcol);
assert.equal(row.intcol, intVal);
assert.isDefined(row.doublecol);
assert.equal(row.doublecol, doubleVal);
assert.isDefined(row.blobcol);
assert.equal(row.blobcol.byteLength, blobVal.byteLength);
}
assert.equal(rowCount, 5);
});
ecdb.withPreparedSqliteStatement("SELECT id,stringcol,intcol,doublecol,blobcol FROM MyTable", (stmt) => {
assert.isTrue(stmt.isReadonly);
let rowCount = 0;
for (const row of stmt) {
rowCount++;
assert.isDefined(row.id);
assert.equal(row.id, rowCount);
assert.isDefined(row.stringcol);
assert.equal(row.stringcol, stringVal);
assert.isDefined(row.intcol);
assert.equal(row.intcol, intVal);
assert.isDefined(row.doublecol);
assert.equal(row.doublecol, doubleVal);
assert.isDefined(row.blobcol);
assert.equal(row.blobcol.byteLength, blobVal.byteLength);
}
assert.equal(rowCount, 5);
});
}
catch (e_1) {
env_1.error = e_1;
env_1.hasError = true;
}
finally {
__disposeResources(env_1);
}
});
it("null values handling", () => {
const env_2 = { stack: [], error: void 0, hasError: false };
try {
const ecdb = __addDisposableResource(env_2, ECDbTestHelper.createECDb(outDir, "bindnull.ecdb"), false);
assert.isTrue(ecdb.isOpen);
ecdb.withSqliteStatement("CREATE TABLE MyTable(id INTEGER PRIMARY KEY, stringcol TEXT, intcol INTEGER, doublecol REAL, blobcol)", (stmt) => {
assert.isFalse(stmt.isReadonly);
assert.equal(stmt.step(), DbResult.BE_SQLITE_DONE);
});
ecdb.withPreparedSqliteStatement("INSERT INTO MyTable(stringcol,intcol,doublecol,blobcol) VALUES(?,?,?,?)", (stmt) => {
assert.isFalse(stmt.isReadonly);
stmt.bindValue(1, undefined);
stmt.bindValue(2, undefined);
stmt.bindValue(3, undefined);
stmt.bindValue(4, undefined);
assert.equal(stmt.step(), DbResult.BE_SQLITE_DONE);
});
ecdb.withPreparedSqliteStatement("INSERT INTO MyTable(stringcol,intcol,doublecol,blobcol) VALUES(?,?,?,?)", (stmt) => {
assert.isFalse(stmt.isReadonly);
stmt.bindValues([]);
assert.equal(stmt.step(), DbResult.BE_SQLITE_DONE);
});
ecdb.withPreparedSqliteStatement("INSERT INTO MyTable(stringcol,intcol,doublecol,blobcol) VALUES(?,?,?,?)", (stmt) => {
assert.isFalse(stmt.isReadonly);
stmt.bindValues([undefined, undefined]);
assert.equal(stmt.step(), DbResult.BE_SQLITE_DONE);
});
ecdb.withPreparedSqliteStatement("INSERT INTO MyTable(stringcol,intcol,doublecol,blobcol) VALUES(:string,:int,:double,:blob)", (stmt) => {
assert.isFalse(stmt.isReadonly);
stmt.bindValue(":string", undefined);
stmt.bindValue(":int", undefined);
stmt.bindValue(":double", undefined);
stmt.bindValue(":blob", undefined);
assert.equal(stmt.step(), DbResult.BE_SQLITE_DONE);
});
ecdb.withPreparedSqliteStatement("INSERT INTO MyTable(stringcol,intcol,doublecol,blobcol) VALUES(?,?,?,?)", (stmt) => {
assert.isFalse(stmt.isReadonly);
stmt.bindValues({});
assert.equal(stmt.step(), DbResult.BE_SQLITE_DONE);
});
ecdb.withPreparedSqliteStatement("INSERT INTO MyTable(stringcol,intcol,doublecol,blobcol) VALUES(?,?,?,?)", (stmt) => {
assert.isFalse(stmt.isReadonly);
stmt.bindValues({ ":string": undefined, ":int": undefined });
assert.equal(stmt.step(), DbResult.BE_SQLITE_DONE);
});
ecdb.withPreparedSqliteStatement("INSERT INTO MyTable(stringcol,intcol,doublecol,blobcol) VALUES(?,?,?,?)", (stmt) => {
stmt.maybeBindString(1, undefined);
stmt.maybeBindInteger(2, undefined);
stmt.maybeBindDouble(3, undefined);
stmt.maybeBindBlob(4, undefined);
assert.equal(stmt.step(), DbResult.BE_SQLITE_DONE);
});
ecdb.saveChanges();
ecdb.withPreparedSqliteStatement("SELECT id,stringcol,intcol,doublecol,blobcol FROM MyTable", (stmt) => {
assert.isTrue(stmt.isReadonly);
let rowCount = 0;
while (stmt.step() === DbResult.BE_SQLITE_ROW) {
rowCount++;
for (let i = 0; i < stmt.getColumnCount(); i++) {
const sqlVal = stmt.getValue(i);
if (i === 0) {
assert.isFalse(sqlVal.isNull);
assert.equal(sqlVal.type, SqliteValueType.Integer);
assert.equal(sqlVal.getInteger(), rowCount);
}
else {
assert.isTrue(sqlVal.isNull);
assert.equal(sqlVal.type, SqliteValueType.Null);
}
}
}
assert.equal(rowCount, 7);
});
}
catch (e_2) {
env_2.error = e_2;
env_2.hasError = true;
}
finally {
__disposeResources(env_2);
}
});
it("ids and guids", () => {
const env_3 = { stack: [], error: void 0, hasError: false };
try {
const ecdb = __addDisposableResource(env_3, ECDbTestHelper.createECDb(outDir, "idsandguids.ecdb"), false);
assert.isTrue(ecdb.isOpen);
ecdb.withSqliteStatement("CREATE TABLE MyTable(id INTEGER PRIMARY KEY, guid BLOB)", (stmt) => {
assert.isFalse(stmt.isReadonly);
assert.equal(stmt.step(), DbResult.BE_SQLITE_DONE);
});
ecdb.withSqliteStatement("INSERT INTO MyTable(id,guid) VALUES(?,?)", (stmt) => {
assert.isFalse(stmt.isReadonly);
stmt.bindValue(1, { id: "0x11" });
stmt.bindValue(2, { guid: "370cea34-8415-4f81-b54c-85040eb3111e" });
assert.equal(stmt.step(), DbResult.BE_SQLITE_DONE);
stmt.reset();
stmt.clearBindings();
stmt.bindValues([{ id: "0x12" }, { guid: "f9f1eb6e-1171-4f45-ba90-55c856056341" }]);
assert.equal(stmt.step(), DbResult.BE_SQLITE_DONE);
stmt.reset();
stmt.clearBindings();
});
ecdb.withSqliteStatement("INSERT INTO MyTable(id,guid) VALUES(:id,:guid)", (stmt) => {
assert.isFalse(stmt.isReadonly);
stmt.bindValue(":id", { id: "0x13" });
stmt.bindValue(":guid", { guid: "370cea34-8415-4f81-b54c-85040eb3111e" });
assert.equal(stmt.step(), DbResult.BE_SQLITE_DONE);
stmt.reset();
stmt.clearBindings();
stmt.bindValues({ ":id": { id: "0x14" }, ":guid": { guid: "f9f1eb6e-1171-4f45-ba90-55c856056341" } });
assert.equal(stmt.step(), DbResult.BE_SQLITE_DONE);
stmt.reset();
stmt.clearBindings();
});
ecdb.withSqliteStatement("SELECT id,guid FROM MyTable", (stmt) => {
assert.isTrue(stmt.isReadonly);
let rowCount = 0;
while (stmt.step() === DbResult.BE_SQLITE_ROW) {
rowCount++;
const expectedId = rowCount + 16;
const idVal = stmt.getValue(0);
assert.equal(expectedId, idVal.getInteger());
assert.equal(typeof (idVal.value), "number");
assert.equal(expectedId, idVal.value);
assert.equal(expectedId, Number.parseInt(idVal.getId(), 16));
const guidVal = stmt.getValue(1);
assert.instanceOf(guidVal.value, Uint8Array);
if (rowCount % 2 !== 0)
assert.equal("370cea34-8415-4f81-b54c-85040eb3111e", guidVal.getGuid());
else
assert.equal("f9f1eb6e-1171-4f45-ba90-55c856056341", guidVal.getGuid());
const row = stmt.getRow();
assert.isDefined(row.id);
assert.equal(typeof (row.id), "number");
assert.equal(expectedId, row.id);
assert.isDefined(row.guid);
assert.instanceOf(row.guid, Uint8Array);
}
assert.equal(4, rowCount);
});
ecdb.saveChanges();
}
catch (e_3) {
env_3.error = e_3;
env_3.hasError = true;
}
finally {
__disposeResources(env_3);
}
});
it("run cached sql", () => {
const env_4 = { stack: [], error: void 0, hasError: false };
try {
const fileName = "sqlitesqlagainstreadonlyconnection.ecdb";
const ecdbPath = path.join(outDir, fileName);
{
const env_5 = { stack: [], error: void 0, hasError: false };
try {
const testEcdb = __addDisposableResource(env_5, ECDbTestHelper.createECDb(outDir, fileName), false);
assert.isTrue(testEcdb.isOpen);
}
catch (e_4) {
env_5.error = e_4;
env_5.hasError = true;
}
finally {
__disposeResources(env_5);
}
}
const ecdb = __addDisposableResource(env_4, new ECDb(), false);
ecdb.openDb(ecdbPath, ECDbOpenMode.Readonly);
assert.isTrue(ecdb.isOpen);
let stmt0;
ecdb.withPreparedSqliteStatement("SELECT Name,StrData FROM be_Prop WHERE Namespace='ec_Db'", (stmt) => {
stmt0 = stmt;
let rowCount = 0;
while (stmt.step() === DbResult.BE_SQLITE_ROW) {
rowCount++;
assert.equal(stmt.getColumnCount(), 2);
const nameVal = stmt.getValue(0);
assert.equal(nameVal.columnName, "Name");
assert.equal(nameVal.type, SqliteValueType.String);
assert.isFalse(nameVal.isNull);
const name = nameVal.getString();
const versionVal = stmt.getValue(1);
assert.equal(versionVal.columnName, "StrData");
assert.equal(versionVal.type, SqliteValueType.String);
assert.isFalse(versionVal.isNull);
const profileVersion = JSON.parse(versionVal.getString());
assert.isTrue(name === "SchemaVersion" || name === "InitialSchemaVersion");
if (name === "SchemaVersion") {
assert.equal(profileVersion.major, 4);
assert.equal(profileVersion.minor, 0);
assert.equal(profileVersion.sub1, 0);
assert.isAtLeast(profileVersion.sub2, 1);
}
else if (name === "InitialSchemaVersion") {
assert.equal(profileVersion.major, 4);
assert.equal(profileVersion.minor, 0);
assert.equal(profileVersion.sub1, 0);
assert.isAtLeast(profileVersion.sub2, 1);
}
}
assert.equal(rowCount, 2);
});
assert.isTrue(stmt0?.isPrepared, "stmt0 is in the cache");
ecdb.resetSqliteCache(3); // reset the statement cache to only hold 3 members so we can exercise overflowing it
assert.isFalse(stmt0?.isPrepared, "reset cache clears stmt0");
let stmt1;
let stmt2;
let stmt2a;
let stmt3;
let stmt4;
let stmt5;
ecdb.withPreparedSqliteStatement("SELECT 1", (stmt) => stmt1 = stmt);
assert.isTrue(stmt1?.isPrepared, "stmt1 is in the cache");
ecdb.withPreparedSqliteStatement("SELECT 2", (stmt) => stmt2 = stmt);
assert.isTrue(stmt2?.isPrepared, "stmt2 is in the cache");
ecdb.withPreparedSqliteStatement("SELECT 3", (stmt) => stmt3 = stmt);
assert.isTrue(stmt3?.isPrepared, "stmt3 is in the cache");
ecdb.withPreparedSqliteStatement("SELECT 4", (stmt) => stmt4 = stmt);
assert.isTrue(stmt4?.isPrepared, "stmt4 is in the cache");
assert.isFalse(stmt1?.isPrepared, "stmt1 was cleared from the cache");
ecdb.withPreparedSqliteStatement("SELECT 2", (stmt) => stmt2a = stmt);
assert.equal(stmt2, stmt2a, "statement 2 gets reused, moved to most recent");
assert.isTrue(stmt3?.isPrepared, "statement 3 is still cached");
ecdb.withPreparedSqliteStatement("SELECT 5", (stmt) => stmt5 = stmt);
assert.isTrue(stmt5?.isPrepared, "statement 5 is cached");
assert.isFalse(stmt3?.isPrepared, "statement 3 was lru and was dropped");
let nested1;
ecdb.withPreparedSqliteStatement("SELECT 1", (stmt) => {
stmt1 = stmt;
ecdb.withPreparedSqliteStatement("SELECT 1", (nested) => nested1 = nested);
});
assert.notEqual(stmt1, nested1, "shouldn't reuse an in-use statement");
assert.isFalse(stmt1?.isPrepared, "outer 1 is not cached");
assert.isTrue(nested1?.isPrepared, "nested1 is cached");
assert.isTrue(stmt2?.isPrepared, "stmt2 is in the cache");
assert.isTrue(stmt5?.isPrepared, "stmt5 is in the cache");
}
catch (e_5) {
env_4.error = e_5;
env_4.hasError = true;
}
finally {
__disposeResources(env_4);
}
});
// This test generate no log when run as suite but is successful when only this fixture run.
it("check prepare logErrors flag", () => {
const env_6 = { stack: [], error: void 0, hasError: false };
try {
const fileName = "logErrors.ecdb";
const ecdbPath = path.join(outDir, fileName);
{
const env_7 = { stack: [], error: void 0, hasError: false };
try {
const testEcdb = __addDisposableResource(env_7, ECDbTestHelper.createECDb(outDir, fileName), false);
assert.isTrue(testEcdb.isOpen);
}
catch (e_6) {
env_7.error = e_6;
env_7.hasError = true;
}
finally {
__disposeResources(env_7);
}
}
const ecdb = __addDisposableResource(env_6, new ECDb(), false);
ecdb.openDb(ecdbPath, ECDbOpenMode.Readonly);
assert.isTrue(ecdb.isOpen);
// expect log message when statement fails
let slm = new SequentialLogMatcher();
slm.append().error().category("BeSQLite").message("Error \"no such table: def (BE_SQLITE_ERROR)\" preparing SQL: SELECT abc FROM def");
assert.throw(() => ecdb.withSqliteStatement("SELECT abc FROM def", () => { }), "no such table: def (BE_SQLITE_ERROR)");
assert.isTrue(slm.finishAndDispose(), "logMatcher should detect log");
// now pass suppress log error which mean we should not get the error
slm = new SequentialLogMatcher();
slm.append().error().category("BeSQLite").message("Error \"no such table: def (BE_SQLITE_ERROR)\" preparing SQL: SELECT abc FROM def");
assert.throw(() => ecdb.withSqliteStatement("SELECT abc FROM def", () => { }, /* logErrors = */ false), "no such table: def (BE_SQLITE_ERROR)");
assert.isFalse(slm.finishAndDispose(), "logMatcher should not detect log");
// expect log message when statement fails
slm = new SequentialLogMatcher();
slm.append().error().category("ECDb").message("ECClass 'abc.def' does not exist or could not be loaded.");
// eslint-disable-next-line @typescript-eslint/no-deprecated
assert.throw(() => ecdb.withPreparedStatement("SELECT abc FROM abc.def", () => { }), "ECClass 'abc.def' does not exist or could not be loaded.");
assert.isTrue(slm.finishAndDispose(), "logMatcher should detect log");
// now pass suppress log error which mean we should not get the error
slm = new SequentialLogMatcher();
slm.append().error().category("ECDb").message("ECClass 'abc.def' does not exist or could not be loaded.");
// eslint-disable-next-line @typescript-eslint/no-deprecated
assert.throw(() => ecdb.withPreparedStatement("SELECT abc FROM abc.def", () => { }, /* logErrors = */ false), ""); // BUG: we do not see error message
assert.isFalse(slm.finishAndDispose(), "logMatcher should not detect log");
}
catch (e_7) {
env_6.error = e_7;
env_6.hasError = true;
}
finally {
__disposeResources(env_6);
}
});
});
//# sourceMappingURL=SqliteStatement.test.js.map