Skip to content

Commit

Permalink
Add RCall/RFunction tests and keep linter happy
Browse files Browse the repository at this point in the history
  • Loading branch information
georgestagg committed Feb 16, 2024
1 parent 23c3b71 commit 86cddda
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 8 deletions.
91 changes: 91 additions & 0 deletions src/tests/webR/webr-main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
REnvironment,
RInteger,
RFunction,
RCall,
} from '../../webR/robj-main';

const webR = new WebR({
Expand Down Expand Up @@ -108,6 +109,19 @@ describe('Evaluate R code', () => {
await expect(throws).rejects.toThrow('This is an error from R!');
});

test('Error conditions are re-thrown in JS when executing an R function', async () => {
const fn = await webR.evalR('sin') as RFunction;
let throws = fn("abc");
await expect(throws).rejects.toThrow('non-numeric argument to mathematical function');
throws = fn.exec("abc");
await expect(throws).rejects.toThrow('non-numeric argument to mathematical function');
});

test('Error conditions are re-thrown in JS when executing an R call', async () => {
const fn = await webR.evalR('quote(sin("abc"))') as RCall;
await expect(fn.eval()).rejects.toThrow('non-numeric argument to mathematical function');
});

test('Capture stdout while capturing R code', async () => {
const shelter = await new webR.Shelter();
const composite = await shelter.captureR('c(1, 2, 4, 6, 12, 24, 36, 48)', {
Expand Down Expand Up @@ -139,6 +153,83 @@ describe('Evaluate R code', () => {
expect(await condMsg.toString()).toContain('This is a warning message');
shelter.purge();
});

test('Capture output when executing an R function', async () => {
const fn = await webR.evalR(`
function(){
print("Hello, stdout!")
message("Hello, message!")
warning("Hello, warning!")
}
`) as RFunction;
const result = await fn.capture({
captureConditions: true,
});

let outType = await result.output.pluck(1, 'type')!;
let outData = await result.output.pluck(1, 'data')!;
expect(await outType.toString()).toEqual('stdout');
expect(await outData.toString()).toContain('Hello, stdout!');

outType = await result.output.pluck(2, 'type')!;
outData = await result.output.pluck(2, 'data', 'message')!;
expect(await outType.toString()).toEqual('message');
expect(await outData.toString()).toContain('Hello, message!');

outType = await result.output.pluck(3, 'type')!;
outData = await result.output.pluck(3, 'data', 'message')!;
expect(await outType.toString()).toEqual('warning');
expect(await outData.toString()).toContain('Hello, warning!');

webR.globalShelter.purge();
});

test('Capturing graphics throws an Error when OffScreenCanvas is unavailable', async () => {
const shelter = await new webR.Shelter();
const throws = shelter.captureR('plot(123)', { captureGraphics: true });
await expect(throws).rejects.toThrow(
'This environment does not have support for OffscreenCanvas.'
);
shelter.purge();
});

test('Capturing graphics with mocked OffScreenCanvas', async () => {
// Mock the OffscreenCanvas interface for testing under Node
await webR.evalRVoid(`
webr::eval_js("
class OffscreenCanvas {
constructor() {}
getContext() {
return {
arc: () => {},
beginPath: () => {},
clearRect: () => {},
clip: () => {},
setLineDash: () => {},
rect: () => {},
restore: () => {},
save: () => {},
stroke: () => {},
};
}
transferToImageBitmap() {
// No ImageBitmap, create a transferable ArrayBuffer in its place
return new ArrayBuffer(8);
}
}
globalThis.OffscreenCanvas = OffscreenCanvas;
")
`);

const shelter = await new webR.Shelter();
const result = await shelter.captureR(`
plot.new();
points(0)
`, { captureGraphics: true });

expect(result.images.length).toBeGreaterThan(0);
shelter.purge();
});
});

describe('Create R objects using serialised form', () => {
Expand Down
4 changes: 2 additions & 2 deletions src/webR/robj-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,7 @@ export class RCall extends RObject {
return Module.webr.evalR(this, { env: objs.baseEnv });
}

capture(options: EvalROptions) {
capture(options: EvalROptions = {}) {
return Module.webr.captureR(this, options);
}
}
Expand Down Expand Up @@ -612,7 +612,7 @@ export class RFunction extends RObject {
}
}

capture(options: EvalROptions, ...args: (WebRDataRaw | RObject)[]) {
capture(options: EvalROptions = {}, ...args: (WebRDataRaw | RObject)[]) {
const prot = { n: 0 };

try {
Expand Down
2 changes: 1 addition & 1 deletion src/webR/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export function isCrossOrigin(urlString: string) {
}

export function isImageBitmap(value: any): value is ImageBitmap {
return (typeof ImageBitmap !== "undefined" && value instanceof ImageBitmap);
return (typeof ImageBitmap !== 'undefined' && value instanceof ImageBitmap);
}

export function throwUnreachable(context?: string) {
Expand Down
10 changes: 5 additions & 5 deletions src/webR/webr-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -644,7 +644,7 @@ function captureR(expr: string | RObject, options: EvalROptions = {}): {
env: objs.globalEnv,
captureStreams: true,
captureConditions: true,
captureGraphics: typeof OffscreenCanvas !== "undefined",
captureGraphics: typeof OffscreenCanvas !== 'undefined',
withAutoprint: false,
throwJsException: true,
withHandlers: true,
Expand All @@ -664,10 +664,10 @@ function captureR(expr: string | RObject, options: EvalROptions = {}): {
const devEnvObj = new REnvironment({});
protectInc(devEnvObj, prot);
if (_options.captureGraphics) {
if (typeof OffscreenCanvas === "undefined") {
if (typeof OffscreenCanvas === 'undefined') {
throw new Error(
"This environment does not have support for OffscreenCanvas. " +
"Consider disabling plot capture using `captureGraphics: false`."
'This environment does not have support for OffscreenCanvas. ' +
'Consider disabling plot capture using `captureGraphics: false`.'
);
}

Expand Down Expand Up @@ -727,7 +727,7 @@ function captureR(expr: string | RObject, options: EvalROptions = {}): {
protectInc(plots, prot);

images = plots.toArray().map((idx) => {
return Module.webr.canvas[idx!].offscreen.transferToImageBitmap()
return Module.webr.canvas[idx!].offscreen.transferToImageBitmap();
});

// Close the device and destroy newly created canvas cache entries
Expand Down

0 comments on commit 86cddda

Please sign in to comment.