From 115dea3d43d666e6e4c0f46146ce4ffc0c2e4c6b Mon Sep 17 00:00:00 2001 From: ledsun Date: Sat, 2 Dec 2023 17:42:19 +0900 Subject: [PATCH 1/4] Add JS::RequireRemote to load external Ruby scripts from the browser Users use JS::RequireRemote#load. The user uses this method to replace the require_relative method. Fix some test codes. - Extract a custom router for integration tests - Split integration test files - Files the body returned by the proxy server --- ext/js/lib/js/require_remote.rb | 85 +++++++++++++++++++ ext/js/lib/js/require_remote/evaluator.rb | 15 ++++ ext/js/lib/js/require_remote/url_resolver.rb | 45 ++++++++++ .../example/require_relative/greeting.rb | 5 ++ .../example/require_relative/index.html | 31 +++++++ .../example/require_relative/main.rb | 3 + .../test-e2e/examples/examples.spec.ts | 23 ++++- ...script.spec.ts => data-eval-async.spec.ts} | 14 +-- .../fixtures/error_on_load_twice.rb | 3 + .../integrations/js-require-remote.spec.ts | 84 ++++++++++++++++++ .../ruby-wasm-wasi/test-e2e/support.ts | 40 +++++++-- 11 files changed, 330 insertions(+), 18 deletions(-) create mode 100644 ext/js/lib/js/require_remote.rb create mode 100644 ext/js/lib/js/require_remote/evaluator.rb create mode 100644 ext/js/lib/js/require_remote/url_resolver.rb create mode 100644 packages/npm-packages/ruby-wasm-wasi/example/require_relative/greeting.rb create mode 100644 packages/npm-packages/ruby-wasm-wasi/example/require_relative/index.html create mode 100644 packages/npm-packages/ruby-wasm-wasi/example/require_relative/main.rb rename packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/{browser-script.spec.ts => data-eval-async.spec.ts} (85%) create mode 100644 packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/fixtures/error_on_load_twice.rb create mode 100644 packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/js-require-remote.spec.ts diff --git a/ext/js/lib/js/require_remote.rb b/ext/js/lib/js/require_remote.rb new file mode 100644 index 0000000000..5c9946cf45 --- /dev/null +++ b/ext/js/lib/js/require_remote.rb @@ -0,0 +1,85 @@ +require "singleton" +require "js" +require_relative "./require_remote/url_resolver" +require_relative "./require_remote/evaluator" + +module JS + # This class is used to load remote Ruby scripts. + # + # == Example + # + # require 'js/require_remote' + # JS::RequireRemote.instance.load("foo") + # + # This class is intended to be used to replace Kernel#require_relative. + # + # == Example + # + # require 'js/require_remote' + # module Kernel + # def require_relative(path) = JS::RequireRemote.instance.load(path) + # end + # + # If you want to load the bundled gem + # + # == Example + # + # require 'js/require_remote' + # module Kernel + # alias original_require_relative require_relative + # + # def require_relative(path) + # caller_path = caller_locations(1, 1).first.absolute_path || '' + # dir = File.dirname(caller_path) + # file = File.absolute_path(path, dir) + # + # original_require_relative(file) + # rescue LoadError + # JS::RequireRemote.instance.load(path) + # end + # end + # + class RequireRemote + include Singleton + + def initialize + base_url = JS.global[:URL].new(JS.global[:location][:href]) + @resolver = URLResolver.new(base_url) + @evaluator = Evaluator.new + end + + # Load the given feature from remote. + def load(relative_feature) + location = @resolver.get_location(relative_feature) + + # Do not load the same URL twice. + return false if @evaluator.evaluated?(location.url[:href].to_s) + + response = JS.global.fetch(location.url).await + unless response[:status].to_i == 200 + raise LoadError.new "cannot load such url -- #{response[:status]} #{location.url}" + end + + # The fetch API may have responded to a redirect response + # and fetched the script from a different URL than the original URL. + # Retrieve the final URL again from the response object. + final_url = response[:url].to_s + + # Do not evaluate the same URL twice. + return false if @evaluator.evaluated?(final_url) + + code = response.text().await.to_s + + evaluate(code, location.filename, final_url) + end + + private + + def evaluate(code, filename, final_url) + @resolver.push(final_url) + @evaluator.evaluate(code, filename, final_url) + @resolver.pop + true + end + end +end diff --git a/ext/js/lib/js/require_remote/evaluator.rb b/ext/js/lib/js/require_remote/evaluator.rb new file mode 100644 index 0000000000..815993f94f --- /dev/null +++ b/ext/js/lib/js/require_remote/evaluator.rb @@ -0,0 +1,15 @@ +module JS + class RequireRemote + # Execute the body of the response and record the URL. + class Evaluator + def evaluate(code, filename, final_url) + Kernel.eval(code, ::Object::TOPLEVEL_BINDING, filename) + $LOADED_FEATURES << final_url + end + + def evaluated?(url) + $LOADED_FEATURES.include?(url) + end + end + end +end diff --git a/ext/js/lib/js/require_remote/url_resolver.rb b/ext/js/lib/js/require_remote/url_resolver.rb new file mode 100644 index 0000000000..7bbaae095c --- /dev/null +++ b/ext/js/lib/js/require_remote/url_resolver.rb @@ -0,0 +1,45 @@ +module JS + class RequireRemote + ScriptLocation = Data.define(:url, :filename) + + # When require_relative is called within a running Ruby script, + # the URL is resolved from a relative file path based on the URL of the running Ruby script. + # It uses a stack to store URLs of running Ruby Script. + # Push the URL onto the stack before executing the new script. + # Then pop it when the script has finished executing. + class URLResolver + def initialize(base_url) + @url_stack = [base_url] + end + + def get_location(relative_feature) + filename = filename_from(relative_feature) + url = resolve(filename) + ScriptLocation.new(url, filename) + end + + def push(url) + @url_stack.push url + end + + def pop() + @url_stack.pop + end + + private + + def filename_from(relative_feature) + if relative_feature.end_with?(".rb") + relative_feature + else + "#{relative_feature}.rb" + end + end + + # Return a URL object of JavaScript. + def resolve(relative_filepath) + JS.global[:URL].new relative_filepath, @url_stack.last + end + end + end +end diff --git a/packages/npm-packages/ruby-wasm-wasi/example/require_relative/greeting.rb b/packages/npm-packages/ruby-wasm-wasi/example/require_relative/greeting.rb new file mode 100644 index 0000000000..7df2752347 --- /dev/null +++ b/packages/npm-packages/ruby-wasm-wasi/example/require_relative/greeting.rb @@ -0,0 +1,5 @@ +class Greeting + def say + puts "Hello, world!" + end +end diff --git a/packages/npm-packages/ruby-wasm-wasi/example/require_relative/index.html b/packages/npm-packages/ruby-wasm-wasi/example/require_relative/index.html new file mode 100644 index 0000000000..0c3a20139d --- /dev/null +++ b/packages/npm-packages/ruby-wasm-wasi/example/require_relative/index.html @@ -0,0 +1,31 @@ + + + + diff --git a/packages/npm-packages/ruby-wasm-wasi/example/require_relative/main.rb b/packages/npm-packages/ruby-wasm-wasi/example/require_relative/main.rb new file mode 100644 index 0000000000..fe43d84d9a --- /dev/null +++ b/packages/npm-packages/ruby-wasm-wasi/example/require_relative/main.rb @@ -0,0 +1,3 @@ +require_relative "greeting" + +Greeting.new.say diff --git a/packages/npm-packages/ruby-wasm-wasi/test-e2e/examples/examples.spec.ts b/packages/npm-packages/ruby-wasm-wasi/test-e2e/examples/examples.spec.ts index 9bdd1b1bef..421b545318 100644 --- a/packages/npm-packages/ruby-wasm-wasi/test-e2e/examples/examples.spec.ts +++ b/packages/npm-packages/ruby-wasm-wasi/test-e2e/examples/examples.spec.ts @@ -14,7 +14,7 @@ test.beforeEach(async ({ context, page }) => { setupDebugLog(context); setupUncaughtExceptionRejection(page); if (process.env.RUBY_NPM_PACKAGE_ROOT) { - setupProxy(context); + setupProxy(context, null); } else { console.info("Testing against CDN deployed files"); const packagePath = path.join(__dirname, "..", "..", "package.json"); @@ -64,3 +64,24 @@ test("script-src/index.html is healthy", async ({ page }) => { await page.waitForEvent("console"); } }); + +// The browser.script.iife.js obtained from CDN does not include the patch to require_relative. +// Skip when testing against the CDN. +if (process.env.RUBY_NPM_PACKAGE_ROOT) { + test("require_relative/index.html is healthy", async ({ page }) => { + // Add a listener to detect errors in the page + page.on("pageerror", (error) => { + console.log(`page error occurs: ${error.message}`); + }); + + const messages: string[] = []; + page.on("console", (msg) => messages.push(msg.text())); + await page.goto("/require_relative/index.html"); + + await waitForRubyVM(page); + const expected = "Hello, world!\n"; + while (messages[messages.length - 1] != expected) { + await page.waitForEvent("console"); + } + }); +} diff --git a/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/browser-script.spec.ts b/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/data-eval-async.spec.ts similarity index 85% rename from packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/browser-script.spec.ts rename to packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/data-eval-async.spec.ts index ad25207430..ba270fe785 100644 --- a/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/browser-script.spec.ts +++ b/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/data-eval-async.spec.ts @@ -5,6 +5,7 @@ import { setupProxy, setupUncaughtExceptionRejection, expectUncaughtException, + resolveBinding, } from "../support"; if (!process.env.RUBY_NPM_PACKAGE_ROOT) { @@ -12,21 +13,10 @@ if (!process.env.RUBY_NPM_PACKAGE_ROOT) { } else { test.beforeEach(async ({ context, page }) => { setupDebugLog(context); - setupProxy(context); + setupProxy(context, null); setupUncaughtExceptionRejection(page); }); - const resolveBinding = async (page: Page, name: string) => { - let checkResolved; - const resolvedValue = new Promise((resolve) => { - checkResolved = resolve; - }); - await page.exposeBinding(name, async (source, v) => { - checkResolved(v); - }); - return async () => await resolvedValue; - }; - test.describe('data-eval="async"', () => { test("JS::Object#await returns value", async ({ page }) => { const resolve = await resolveBinding(page, "checkResolved"); diff --git a/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/fixtures/error_on_load_twice.rb b/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/fixtures/error_on_load_twice.rb new file mode 100644 index 0000000000..7b4ce92112 --- /dev/null +++ b/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/fixtures/error_on_load_twice.rb @@ -0,0 +1,3 @@ +raise "load twice" if defined?(ALREADY_LOADED) + +ALREADY_LOADED = true diff --git a/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/js-require-remote.spec.ts b/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/js-require-remote.spec.ts new file mode 100644 index 0000000000..61fdf14c8e --- /dev/null +++ b/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/js-require-remote.spec.ts @@ -0,0 +1,84 @@ +import fs from "fs"; +import path from "path"; +import { test, expect } from "@playwright/test"; +import { setupDebugLog, setupProxy, resolveBinding } from "../support"; + +if (!process.env.RUBY_NPM_PACKAGE_ROOT) { + test.skip("skip", () => {}); +} else { + test.beforeEach(async ({ context }) => { + setupDebugLog(context); + setupProxy(context, (route, relativePath, mockedPath) => { + if (relativePath.match("fixtures")) { + route.fulfill({ + path: path.join("./test-e2e/integrations", relativePath), + }); + } else if (fs.existsSync(mockedPath)) { + route.fulfill({ + path: mockedPath, + }); + } else { + route.fulfill({ + status: 404, + }); + } + }); + }); + + test.describe("JS::RequireRemote#load", () => { + test("JS::RequireRemote#load returns true", async ({ page }) => { + const resolve = await resolveBinding(page, "checkResolved"); + await page.goto( + "https://cdn.jsdelivr.net/npm/@ruby/head-wasm-wasi@latest/dist/", + ); + await page.setContent(` + + + `); + + expect(await resolve()).toBe(true); + }); + + test("JS::RequireRemote#load returns false when same gem is loaded twice", async ({ + page, + }) => { + const resolve = await resolveBinding(page, "checkResolved"); + await page.goto( + "https://cdn.jsdelivr.net/npm/@ruby/head-wasm-wasi@latest/dist/", + ); + await page.setContent(` + + + `); + + expect(await resolve()).toBe(false); + }); + + test("JS::RequireRemote#load throws error when gem is not found", async ({ + page, + }) => { + // Opens the URL that will be used as the basis for determining the relative URL. + await page.goto( + "https://cdn.jsdelivr.net/npm/@ruby/head-wasm-wasi@latest/dist/", + ); + await page.setContent(` + + + `); + + const error = await page.waitForEvent("pageerror"); + expect(error.message).toMatch(/cannot load such url -- .+\/foo.rb/); + }); + }); +} diff --git a/packages/npm-packages/ruby-wasm-wasi/test-e2e/support.ts b/packages/npm-packages/ruby-wasm-wasi/test-e2e/support.ts index e37cf27f22..0ec784f6be 100644 --- a/packages/npm-packages/ruby-wasm-wasi/test-e2e/support.ts +++ b/packages/npm-packages/ruby-wasm-wasi/test-e2e/support.ts @@ -1,4 +1,4 @@ -import { BrowserContext, Page, expect } from "@playwright/test"; +import { BrowserContext, Page, Route, expect } from "@playwright/test"; import path from "path"; export const waitForRubyVM = async (page: Page) => { @@ -17,16 +17,35 @@ export const setupDebugLog = (context: BrowserContext) => { } }; -export const setupProxy = (context: BrowserContext) => { +type CustomRouter = ( + route: Route, + relativePath: string, + mockedPath: string, +) => void; +export const setupProxy = ( + context: BrowserContext, + customRouter: CustomRouter | null, +) => { const cdnPattern = /cdn.jsdelivr.net\/npm\/@ruby\/.+-wasm-wasi@.+\/dist\/(.+)/; + context.route(cdnPattern, (route) => { const request = route.request(); console.log(">> [MOCK]", request.method(), request.url()); const relativePath = request.url().match(cdnPattern)[1]; - route.fulfill({ - path: path.join(process.env.RUBY_NPM_PACKAGE_ROOT, "dist", relativePath), - }); + const mockedPath = path.join( + process.env.RUBY_NPM_PACKAGE_ROOT, + "dist", + relativePath, + ); + + if (customRouter) { + customRouter(route, relativePath, mockedPath); + } else { + route.fulfill({ + path: mockedPath, + }); + } }); }; @@ -44,3 +63,14 @@ export const { setupUncaughtExceptionRejection, expectUncaughtException } = }, }; })(); + +export const resolveBinding = async (page: Page, name: string) => { + let checkResolved; + const resolvedValue = new Promise((resolve) => { + checkResolved = resolve; + }); + await page.exposeBinding(name, async (source, v) => { + checkResolved(v); + }); + return async () => await resolvedValue; +}; From acbf323a1579f89db6f89ef29c23d421740c1889 Mon Sep 17 00:00:00 2001 From: ledsun Date: Sun, 24 Dec 2023 22:59:00 +0900 Subject: [PATCH 2/4] Stop extending setupProxy. Add Playwright routing instead. --- .../test-e2e/examples/examples.spec.ts | 2 +- .../integrations/data-eval-async.spec.ts | 2 +- .../integrations/js-require-remote.spec.ts | 45 +++++++++++-------- .../ruby-wasm-wasi/test-e2e/support.ts | 23 +++------- 4 files changed, 34 insertions(+), 38 deletions(-) diff --git a/packages/npm-packages/ruby-wasm-wasi/test-e2e/examples/examples.spec.ts b/packages/npm-packages/ruby-wasm-wasi/test-e2e/examples/examples.spec.ts index 421b545318..af6b71059f 100644 --- a/packages/npm-packages/ruby-wasm-wasi/test-e2e/examples/examples.spec.ts +++ b/packages/npm-packages/ruby-wasm-wasi/test-e2e/examples/examples.spec.ts @@ -14,7 +14,7 @@ test.beforeEach(async ({ context, page }) => { setupDebugLog(context); setupUncaughtExceptionRejection(page); if (process.env.RUBY_NPM_PACKAGE_ROOT) { - setupProxy(context, null); + setupProxy(context); } else { console.info("Testing against CDN deployed files"); const packagePath = path.join(__dirname, "..", "..", "package.json"); diff --git a/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/data-eval-async.spec.ts b/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/data-eval-async.spec.ts index ba270fe785..96e6fbe2cb 100644 --- a/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/data-eval-async.spec.ts +++ b/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/data-eval-async.spec.ts @@ -13,7 +13,7 @@ if (!process.env.RUBY_NPM_PACKAGE_ROOT) { } else { test.beforeEach(async ({ context, page }) => { setupDebugLog(context); - setupProxy(context, null); + setupProxy(context); setupUncaughtExceptionRejection(page); }); diff --git a/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/js-require-remote.spec.ts b/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/js-require-remote.spec.ts index 61fdf14c8e..06e5306062 100644 --- a/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/js-require-remote.spec.ts +++ b/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/js-require-remote.spec.ts @@ -1,28 +1,35 @@ import fs from "fs"; import path from "path"; import { test, expect } from "@playwright/test"; -import { setupDebugLog, setupProxy, resolveBinding } from "../support"; +import { + setupDebugLog, + setupProxy, + setupUncaughtExceptionRejection, + expectUncaughtException, + resolveBinding, +} from "../support"; if (!process.env.RUBY_NPM_PACKAGE_ROOT) { test.skip("skip", () => {}); } else { - test.beforeEach(async ({ context }) => { + test.beforeEach(async ({ context, page }) => { setupDebugLog(context); - setupProxy(context, (route, relativePath, mockedPath) => { - if (relativePath.match("fixtures")) { - route.fulfill({ - path: path.join("./test-e2e/integrations", relativePath), - }); - } else if (fs.existsSync(mockedPath)) { - route.fulfill({ - path: mockedPath, - }); - } else { - route.fulfill({ - status: 404, - }); - } + setupProxy(context); + + context.route(/fixtures/, (route) => { + const filename = path.basename(route.request().url()); + route.fulfill({ + path: path.join("./test-e2e/integrations/fixtures", filename), + }); + }); + + context.route(/not_found/, (route) => { + route.fulfill({ + status: 404, + }); }); + + setupUncaughtExceptionRejection(page); }); test.describe("JS::RequireRemote#load", () => { @@ -64,6 +71,8 @@ if (!process.env.RUBY_NPM_PACKAGE_ROOT) { test("JS::RequireRemote#load throws error when gem is not found", async ({ page, }) => { + expectUncaughtException(page); + // Opens the URL that will be used as the basis for determining the relative URL. await page.goto( "https://cdn.jsdelivr.net/npm/@ruby/head-wasm-wasi@latest/dist/", @@ -73,12 +82,12 @@ if (!process.env.RUBY_NPM_PACKAGE_ROOT) { `); const error = await page.waitForEvent("pageerror"); - expect(error.message).toMatch(/cannot load such url -- .+\/foo.rb/); + expect(error.message).toMatch(/cannot load such url -- .+\/not_found.rb/); }); }); } diff --git a/packages/npm-packages/ruby-wasm-wasi/test-e2e/support.ts b/packages/npm-packages/ruby-wasm-wasi/test-e2e/support.ts index 0ec784f6be..72c13075e0 100644 --- a/packages/npm-packages/ruby-wasm-wasi/test-e2e/support.ts +++ b/packages/npm-packages/ruby-wasm-wasi/test-e2e/support.ts @@ -1,4 +1,4 @@ -import { BrowserContext, Page, Route, expect } from "@playwright/test"; +import { BrowserContext, Page, expect } from "@playwright/test"; import path from "path"; export const waitForRubyVM = async (page: Page) => { @@ -17,18 +17,9 @@ export const setupDebugLog = (context: BrowserContext) => { } }; -type CustomRouter = ( - route: Route, - relativePath: string, - mockedPath: string, -) => void; -export const setupProxy = ( - context: BrowserContext, - customRouter: CustomRouter | null, -) => { +export const setupProxy = (context: BrowserContext) => { const cdnPattern = /cdn.jsdelivr.net\/npm\/@ruby\/.+-wasm-wasi@.+\/dist\/(.+)/; - context.route(cdnPattern, (route) => { const request = route.request(); console.log(">> [MOCK]", request.method(), request.url()); @@ -39,13 +30,9 @@ export const setupProxy = ( relativePath, ); - if (customRouter) { - customRouter(route, relativePath, mockedPath); - } else { - route.fulfill({ - path: mockedPath, - }); - } + route.fulfill({ + path: mockedPath, + }); }); }; From 45755c484a05e1fcdd2cc84c6a2efe029ea6151a Mon Sep 17 00:00:00 2001 From: ledsun Date: Sun, 24 Dec 2023 22:49:56 +0900 Subject: [PATCH 3/4] Add a test case that repeats require_relative recursively. --- .../fixtures/recursive_require.rb | 1 + .../fixtures/recursive_require/a.rb | 1 + .../fixtures/recursive_require/b.rb | 7 ++++ .../integrations/js-require-remote.spec.ts | 32 +++++++++++++++++-- 4 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/fixtures/recursive_require.rb create mode 100644 packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/fixtures/recursive_require/a.rb create mode 100644 packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/fixtures/recursive_require/b.rb diff --git a/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/fixtures/recursive_require.rb b/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/fixtures/recursive_require.rb new file mode 100644 index 0000000000..c14cbb2386 --- /dev/null +++ b/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/fixtures/recursive_require.rb @@ -0,0 +1 @@ +require_relative "./recursive_require/a.rb" diff --git a/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/fixtures/recursive_require/a.rb b/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/fixtures/recursive_require/a.rb new file mode 100644 index 0000000000..9f68c6a221 --- /dev/null +++ b/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/fixtures/recursive_require/a.rb @@ -0,0 +1 @@ +require_relative "./b.rb" diff --git a/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/fixtures/recursive_require/b.rb b/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/fixtures/recursive_require/b.rb new file mode 100644 index 0000000000..511db1e29b --- /dev/null +++ b/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/fixtures/recursive_require/b.rb @@ -0,0 +1,7 @@ +module RecursiveRequire + class B + def message + "Hello from RecursiveRequire::B" + end + end +end diff --git a/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/js-require-remote.spec.ts b/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/js-require-remote.spec.ts index 06e5306062..2bd1e0ed17 100644 --- a/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/js-require-remote.spec.ts +++ b/packages/npm-packages/ruby-wasm-wasi/test-e2e/integrations/js-require-remote.spec.ts @@ -16,10 +16,13 @@ if (!process.env.RUBY_NPM_PACKAGE_ROOT) { setupDebugLog(context); setupProxy(context); - context.route(/fixtures/, (route) => { - const filename = path.basename(route.request().url()); + const fixturesPattern = /fixtures\/(.+)/; + context.route(fixturesPattern, (route) => { + const subPath = route.request().url().match(fixturesPattern)[1]; + const mockedPath = path.join("./test-e2e/integrations/fixtures", subPath); + route.fulfill({ - path: path.join("./test-e2e/integrations/fixtures", filename), + path: mockedPath, }); }); @@ -89,5 +92,28 @@ if (!process.env.RUBY_NPM_PACKAGE_ROOT) { const error = await page.waitForEvent("pageerror"); expect(error.message).toMatch(/cannot load such url -- .+\/not_found.rb/); }); + + test("JS::RequireRemote#load recursively loads dependencies", async ({ + page, + }) => { + const resolve = await resolveBinding(page, "checkResolved"); + await page.goto( + "https://cdn.jsdelivr.net/npm/@ruby/head-wasm-wasi@latest/dist/", + ); + await page.setContent(` + + + `); + + expect(await resolve()).toBe("Hello from RecursiveRequire::B"); + }); }); } From b9db613d67e78dd83a1734600c87758df7f1f456 Mon Sep 17 00:00:00 2001 From: ledsun Date: Tue, 26 Dec 2023 09:02:28 +0900 Subject: [PATCH 4/4] Add unit tests for JS::RequireRemote::URLResolver --- .../ruby-wasm-wasi/test/test_unit.rb | 1 + .../test/unit/require_remote/url_resolver.rb | 66 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 packages/npm-packages/ruby-wasm-wasi/test/unit/require_remote/url_resolver.rb diff --git a/packages/npm-packages/ruby-wasm-wasi/test/test_unit.rb b/packages/npm-packages/ruby-wasm-wasi/test/test_unit.rb index e04b07f434..f75898c087 100644 --- a/packages/npm-packages/ruby-wasm-wasi/test/test_unit.rb +++ b/packages/npm-packages/ruby-wasm-wasi/test/test_unit.rb @@ -8,3 +8,4 @@ require_relative "./unit/test_array" require_relative "./unit/test_hash" require_relative "./unit/test_nil_class" +require_relative "./unit/require_remote/url_resolver" diff --git a/packages/npm-packages/ruby-wasm-wasi/test/unit/require_remote/url_resolver.rb b/packages/npm-packages/ruby-wasm-wasi/test/unit/require_remote/url_resolver.rb new file mode 100644 index 0000000000..8ed274161b --- /dev/null +++ b/packages/npm-packages/ruby-wasm-wasi/test/unit/require_remote/url_resolver.rb @@ -0,0 +1,66 @@ +require "test-unit" +require "js" +require "js/require_remote" + +class TestURLResolver < Test::Unit::TestCase + def test_get_location + url_resolver = JS::RequireRemote::URLResolver.new("https://example.com") + script_location = url_resolver.get_location("foo.rb") + assert_equal "https://example.com/foo.rb", script_location.url.to_s + assert_equal "foo.rb", script_location.filename + end + + def test_get_location_with_relative_path + url_resolver = JS::RequireRemote::URLResolver.new("https://example.com") + script_location = url_resolver.get_location("./foo.rb") + assert_equal "https://example.com/foo.rb", script_location.url.to_s + assert_equal "./foo.rb", script_location.filename + end + + def test_get_location_with_relative_path_and_filename + url_resolver = JS::RequireRemote::URLResolver.new("https://example.com/bar.rb") + script_location = url_resolver.get_location("./foo.rb") + assert_equal "https://example.com/foo.rb", script_location.url.to_s + assert_equal "./foo.rb", script_location.filename + end + + def test_get_location_with_relative_path_and_filename_without_extension + url_resolver = JS::RequireRemote::URLResolver.new("https://example.com/bar") + script_location = url_resolver.get_location("./foo") + assert_equal "https://example.com/foo.rb", script_location.url.to_s + assert_equal "./foo.rb", script_location.filename + end + + def test_get_location_with_relative_path_and_directory + url_resolver = JS::RequireRemote::URLResolver.new("https://example.com/bar/") + script_location = url_resolver.get_location("./foo.rb") + assert_equal "https://example.com/bar/foo.rb", script_location.url.to_s + assert_equal "./foo.rb", script_location.filename + end + + def test_get_location_with_backward_relative_path + url_resolver = JS::RequireRemote::URLResolver.new("https://example.com/bar/") + script_location = url_resolver.get_location("../foo.rb") + assert_equal "https://example.com/foo.rb", script_location.url.to_s + assert_equal "../foo.rb", script_location.filename + end + + def test_get_location_with_backward_relative_path_and_filename + url_resolver = JS::RequireRemote::URLResolver.new("https://example.com/baz.rb") + script_location = url_resolver.get_location("../foo.rb") + assert_equal "https://example.com/foo.rb", script_location.url.to_s + assert_equal "../foo.rb", script_location.filename + end + + def test_push_and_pop + url_resolver = JS::RequireRemote::URLResolver.new("https://example.com") + url_resolver.push("https://example.com/foo/bar.rb") + script_location = url_resolver.get_location("./baz.rb") + assert_equal "https://example.com/foo/baz.rb", script_location.url.to_s + assert_equal "./baz.rb", script_location.filename + url_resolver.pop + script_location = url_resolver.get_location("./baz.rb") + assert_equal "https://example.com/baz.rb", script_location.url.to_s + assert_equal "./baz.rb", script_location.filename + end +end