Skip to content

Commit

Permalink
add polyfill tests
Browse files Browse the repository at this point in the history
  • Loading branch information
tido64 committed Aug 31, 2023
1 parent 13ecde9 commit 46b7e5b
Show file tree
Hide file tree
Showing 24 changed files with 265 additions and 16 deletions.
3 changes: 2 additions & 1 deletion incubator/polyfills/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"scripts": {
"build": "rnx-kit-scripts build",
"format": "rnx-kit-scripts format",
"lint": "rnx-kit-scripts lint"
"lint": "rnx-kit-scripts lint",
"test": "rnx-kit-scripts test"
},
"dependencies": {
"@babel/core": "^7.0.0",
Expand Down
32 changes: 18 additions & 14 deletions incubator/polyfills/src/dependency.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { error } from "@rnx-kit/console";
import { readPackage } from "@rnx-kit/tools-node";
import * as fs from "fs";
import * as path from "path";
import type { Context } from "./types";

Expand All @@ -17,13 +18,13 @@ function getDependencies({ projectRoot }: Context): string[] {
return Array.from(dependencies);
}

function isValidPath(p: string): boolean {
return (
Boolean(p) &&
!p.startsWith("..") &&
!p.startsWith("/") &&
!/^[A-Za-z]:/.test(p)
);
export function resolvePath(fromDir: string, p: unknown): string | null {
if (typeof p !== "string" || !p) {
return null;
}

const resolved = path.resolve(fromDir, p);
return resolved.startsWith(fromDir) ? resolved : null;
}

export function getDependencyPolyfills(context: Context): string[] {
Expand All @@ -36,14 +37,17 @@ export function getDependencyPolyfills(context: Context): string[] {
try {
const config = require.resolve(`${name}/react-native.config.js`, options);
const polyfill = require(config).dependency?.api?.polyfill;
if (typeof polyfill === "string") {
if (!isValidPath(polyfill)) {
error(`${name}: invalid polyfill path: ${polyfill}`);
continue;
}

polyfills.push(path.resolve(path.dirname(config), polyfill));
const absolutePath = resolvePath(path.dirname(config), polyfill);
if (!absolutePath) {
error(`${name}: invalid polyfill path: ${polyfill}`);
continue;
}
if (!fs.existsSync(absolutePath)) {
error(`${name}: no such polyfill: ${polyfill}`);
continue;
}

polyfills.push(absolutePath);
} catch (_) {
// ignore
}
Expand Down
4 changes: 3 additions & 1 deletion incubator/polyfills/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ module.exports = declare((api: ConfigAPI) => {
const polyfills = getDependencyPolyfills({ projectRoot: context.cwd });
const importPolyfill = babelTemplate(`import %%source%%;`);

for (const polyfill of polyfills) {
// Add polyfills in reverse order because we're unshifting
for (let i = polyfills.length - 1; i >= 0; --i) {
const polyfill = polyfills[i];
path.unshiftContainer(
"body",
importPolyfill({ source: t.stringLiteral(polyfill) })
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions incubator/polyfills/test/__fixtures__/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "AwesomeGame",
"version": "1.0.0",
"dependencies": {
"@react-native-webapis/battery-status": "*"
},
"peerDependencies": {
"this-should-be-ignored": "*"
},
"devDependencies": {
"@react-native-webapis/gamepad": "*",
"invalid-polyfill-boolean": "*",
"invalid-polyfill-boundary": "*",
"invalid-polyfill-missing": "*"
}
}
14 changes: 14 additions & 0 deletions incubator/polyfills/test/__mocks__/chalk.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const chalk = jest.createMockFromModule("chalk");

function passthrough(s) {
return s;
}

chalk.cyan = passthrough;
chalk.cyan.bold = passthrough;
chalk.red = passthrough;
chalk.red.bold = passthrough;
chalk.yellow = passthrough;
chalk.yellow.bold = passthrough;

module.exports = chalk;
85 changes: 85 additions & 0 deletions incubator/polyfills/test/dependency.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import * as os from "node:os";
import * as path from "node:path";
import { getDependencyPolyfills, resolvePath } from "../src/dependency";

describe("getDependencyPolyfills", () => {
const consoleErrorSpy = jest.spyOn(global.console, "error");

beforeEach(() => {
consoleErrorSpy.mockReset();
});

afterAll(() => {
jest.clearAllMocks();
});

test("collects polyfills from included valid packages", () => {
const context = {
projectRoot: path.join(__dirname, "__fixtures__"),
};

expect(getDependencyPolyfills(context).sort()).toEqual([
expect.stringMatching(
/[/\\]node_modules[/\\]@react-native-webapis[/\\]battery-status[/\\]polyfill.js$/
),
expect.stringMatching(
/[/\\]node_modules[/\\]@react-native-webapis[/\\]gamepad[/\\]polyfill.js$/
),
]);

expect(consoleErrorSpy).toBeCalledTimes(3);
expect(consoleErrorSpy).toBeCalledWith(
"error",
expect.stringMatching(/^invalid-polyfill-boolean: invalid polyfill path/)
);
expect(consoleErrorSpy).toBeCalledWith(
"error",
expect.stringMatching(/^invalid-polyfill-boundary: invalid polyfill path/)
);
expect(consoleErrorSpy).toBeCalledWith(
"error",
expect.stringMatching(/^invalid-polyfill-missing: no such polyfill/)
);
});
});

describe("resolvePath", () => {
test("rejects invalid paths", () => {
expect(resolvePath(__dirname, "")).toBe(null);
expect(resolvePath(__dirname, [])).toBe(null);
expect(resolvePath(__dirname, false)).toBe(null);
expect(resolvePath(__dirname, null)).toBe(null);
expect(resolvePath(__dirname, undefined)).toBe(null);
});

test("rejects paths outside the package boundary", () => {
expect(resolvePath(__dirname, "/bin/sh")).toBe(null);

expect(resolvePath(__dirname, "../../bin/sh")).toBe(null);
expect(resolvePath(__dirname, "../bin/sh")).toBe(null);
expect(resolvePath(__dirname, "./../bin/sh")).toBe(null);

if (os.platform() === "win32") {
const p = "C:\\Windows\\System32\\cmd.exe";
expect(resolvePath(__dirname, p)).toBe(null);
expect(resolvePath(__dirname, p.toLowerCase())).toBe(null);

expect(resolvePath(__dirname, "..\\..\\Windows\\System32\\cmd.exe")).toBe(
null
);
expect(resolvePath(__dirname, "..\\Windows\\System32\\cmd.exe")).toBe(
null
);
expect(resolvePath(__dirname, ".\\..\\Windows\\System32\\cmd.exe")).toBe(
null
);
}
});

test("accepts paths inside the package boundary", () => {
expect(resolvePath(__dirname, "./index.js")).not.toBe(null);
expect(resolvePath(__dirname, "./lib/index.js")).not.toBe(null);
expect(resolvePath(__dirname, "index.js")).not.toBe(null);
expect(resolvePath(__dirname, "lib/index.js")).not.toBe(null);
});
});
52 changes: 52 additions & 0 deletions incubator/polyfills/test/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import * as babel from "@babel/core";
import * as path from "node:path";

describe("polyfills", () => {
const currentWorkingDir = process.cwd();
const transformOptions = {
plugins: [require(path.join(__dirname, "..", "lib", "index.js"))],
};

function setFixture(): void {
process.chdir(path.join(__dirname, "__fixtures__"));
}

function transform(code: string): string | null | undefined {
const result = babel.transformSync(code, transformOptions);
return result?.code;
}

afterEach(() => {
process.chdir(currentWorkingDir);
});

test("is noop without trigger word", () => {
const code = "console.log(0);";
expect(transform(code)).toBe(code);

setFixture();
expect(transform(code)).toBe(code);
});

test("is noop without polyfills", () => {
const code = "// @react-native-webapis\nconsole.log(0);";
expect(transform(code)).toBe(code);
});

test("injects polyfills at the top", () => {
setFixture();

const code = ["// @react-native-webapis", "console.log(0);"];
const result = transform(code.join("\n"));

expect(result?.split("\n")).toEqual([
expect.stringMatching(
/^import ".*?[/\\]{1,2}@react-native-webapis[/\\]{1,2}battery-status[/\\]{1,2}polyfill.js";$/
),
expect.stringMatching(
/^import ".*?[/\\]{1,2}@react-native-webapis[/\\]{1,2}gamepad[/\\]{1,2}polyfill.js";$/
),
...code,
]);
});
});

0 comments on commit 46b7e5b

Please sign in to comment.