Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Workletizable Context Objects #6229

Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import React, { useEffect } from 'react';
import { View } from 'react-native';
import { useSharedValue, runOnUI } from 'react-native-reanimated';
import {
render,
wait,
describe,
getRegisteredValue,
registerValue,
test,
expect,
} from '../../ReanimatedRuntimeTestsRunner/RuntimeTestsApi';

const SHARED_VALUE_REF = 'SHARED_VALUE_REF';

describe('Test context objects', () => {
test('methods are workletized', async () => {
const ExampleComponent = () => {
const output = useSharedValue<number | null>(null);
registerValue(SHARED_VALUE_REF, output);
const contextObject = {
foo() {
return 1;
},
__workletObject: true,
};

useEffect(() => {
runOnUI(() => {
output.value = contextObject.foo();
})();
});

return <View />;
};
await render(<ExampleComponent />);
await wait(100);
const sharedValue = await getRegisteredValue(SHARED_VALUE_REF);
expect(sharedValue.onUI).toBe(1);
});

test('properties are workletized', async () => {
const ExampleComponent = () => {
const output = useSharedValue<number | null>(null);
registerValue(SHARED_VALUE_REF, output);
const contextObject = {
foo: () => 1,
__workletObject: true,
};

useEffect(() => {
runOnUI(() => {
output.value = contextObject.foo();
})();
});

return <View />;
};
await render(<ExampleComponent />);
await wait(100);
const sharedValue = await getRegisteredValue(SHARED_VALUE_REF);
expect(sharedValue.onUI).toBe(1);
});

test('methods preserve implicit context', async () => {
const ExampleComponent = () => {
const output = useSharedValue<number | null>(null);
registerValue(SHARED_VALUE_REF, output);
const contextObject = {
foo() {
return 1;
},
bar() {
return this.foo() + 1;
},
__workletObject: true,
};

useEffect(() => {
runOnUI(() => {
output.value = contextObject.bar();
})();
});

return <View />;
};
await render(<ExampleComponent />);
await wait(100);
const sharedValue = await getRegisteredValue(SHARED_VALUE_REF);
expect(sharedValue.onUI).toBe(2);
});

test('methods preserve explicit context', async () => {
const ExampleComponent = () => {
const output = useSharedValue<number | null>(null);
registerValue(SHARED_VALUE_REF, output);
const contextObject = {
foo() {
return 1;
},
bar() {
return this.foo.call(contextObject) + 1;
},
__workletObject: true,
};

useEffect(() => {
runOnUI(() => {
output.value = contextObject.bar();
})();
});

return <View />;
};
await render(<ExampleComponent />);
await wait(100);
const sharedValue = await getRegisteredValue(SHARED_VALUE_REF);
expect(sharedValue.onUI).toBe(2);
});

test('methods change the state of the object', async () => {
const ExampleComponent = () => {
const output = useSharedValue<number | null>(null);
registerValue(SHARED_VALUE_REF, output);
const contextObject = {
foo: 1,
bar() {
this.foo += 1;
},
__workletObject: true,
};

useEffect(() => {
runOnUI(() => {
contextObject.bar();
output.value = contextObject.foo;
})();
});

return <View />;
};
await render(<ExampleComponent />);
await wait(100);
const sharedValue = await getRegisteredValue(SHARED_VALUE_REF);
expect(sharedValue.onUI).toBe(2);
});

test("the object doesn't persist in memory", async () => {
const ExampleComponent = () => {
const output = useSharedValue<number | null>(null);
registerValue(SHARED_VALUE_REF, output);
const contextObject = {
foo: 1,
bar() {
this.foo += 1;
},
__workletObject: true,
};

useEffect(() => {
runOnUI(() => {
contextObject.bar();
output.value = contextObject.foo;
})();
});

return <View />;
};
await render(<ExampleComponent />);
await wait(100);
const sharedValue = await getRegisteredValue(SHARED_VALUE_REF);
expect(sharedValue.onUI).toBe(2);
await render(<ExampleComponent />);
await wait(100);
const sharedValue2 = await getRegisteredValue(SHARED_VALUE_REF);
expect(sharedValue2.onUI).toBe(2);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,128 @@ var f = function () {
}();"
`;

exports[`babel plugin for context objects creates factories 1`] = `
"var _worklet_14630842371699_init_data = {
code: "function __workletObjectFactory_null1(){return{bar:function(){return'bar';}};}",
location: "/dev/null",
sourceMap: "\\"mock source map\\"",
version: "x.y.z"
};
var foo = {
bar: function bar() {
return 'bar';
},
__workletObjectFactory: function () {
var _e = [new global.Error(), 1, -27];
var __workletObjectFactory_null1 = function __workletObjectFactory_null1() {
return {
bar: function bar() {
return 'bar';
}
};
};
__workletObjectFactory_null1.__closure = {};
__workletObjectFactory_null1.__workletHash = 14630842371699;
__workletObjectFactory_null1.__initData = _worklet_14630842371699_init_data;
__workletObjectFactory_null1.__stackDetails = _e;
return __workletObjectFactory_null1;
}()
};"
`;

exports[`babel plugin for context objects preserves bindings 1`] = `
"var _worklet_13432710970622_init_data = {
code: "function __workletObjectFactory_null1(){return{bar:function(){return'bar';},foobar:function(){return this.bar();}};}",
location: "/dev/null",
sourceMap: "\\"mock source map\\"",
version: "x.y.z"
};
var foo = {
bar: function bar() {
return 'bar';
},
foobar: function foobar() {
return this.bar();
},
__workletObjectFactory: function () {
var _e = [new global.Error(), 1, -27];
var __workletObjectFactory_null1 = function __workletObjectFactory_null1() {
return {
bar: function bar() {
return 'bar';
},
foobar: function foobar() {
return this.bar();
}
};
};
__workletObjectFactory_null1.__closure = {};
__workletObjectFactory_null1.__workletHash = 13432710970622;
__workletObjectFactory_null1.__initData = _worklet_13432710970622_init_data;
__workletObjectFactory_null1.__stackDetails = _e;
return __workletObjectFactory_null1;
}()
};"
`;

exports[`babel plugin for context objects removes marker 1`] = `
"var _worklet_14630842371699_init_data = {
code: "function __workletObjectFactory_null1(){return{bar:function(){return'bar';}};}",
location: "/dev/null",
sourceMap: "\\"mock source map\\"",
version: "x.y.z"
};
var foo = {
bar: function bar() {
return 'bar';
},
__workletObjectFactory: function () {
var _e = [new global.Error(), 1, -27];
var __workletObjectFactory_null1 = function __workletObjectFactory_null1() {
return {
bar: function bar() {
return 'bar';
}
};
};
__workletObjectFactory_null1.__closure = {};
__workletObjectFactory_null1.__workletHash = 14630842371699;
__workletObjectFactory_null1.__initData = _worklet_14630842371699_init_data;
__workletObjectFactory_null1.__stackDetails = _e;
return __workletObjectFactory_null1;
}()
};"
`;

exports[`babel plugin for context objects workletizes regardless of marker value 1`] = `
"var _worklet_14630842371699_init_data = {
code: "function __workletObjectFactory_null1(){return{bar:function(){return'bar';}};}",
location: "/dev/null",
sourceMap: "\\"mock source map\\"",
version: "x.y.z"
};
var foo = {
bar: function bar() {
return 'bar';
},
__workletObjectFactory: function () {
var _e = [new global.Error(), 1, -27];
var __workletObjectFactory_null1 = function __workletObjectFactory_null1() {
return {
bar: function bar() {
return 'bar';
}
};
};
__workletObjectFactory_null1.__closure = {};
__workletObjectFactory_null1.__workletHash = 14630842371699;
__workletObjectFactory_null1.__initData = _worklet_14630842371699_init_data;
__workletObjectFactory_null1.__stackDetails = _e;
return __workletObjectFactory_null1;
}()
};"
`;

exports[`babel plugin for debugging does inject location for worklets in dev builds 1`] = `
"var _worklet_8623346549410_init_data = {
code: "function null1(){const x=1;}",
Expand Down
66 changes: 66 additions & 0 deletions packages/react-native-reanimated/__tests__/plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2282,4 +2282,70 @@ describe('babel plugin', () => {
expect(code).toMatchSnapshot();
});
});

describe('for context objects', () => {
it('removes marker', () => {
const input = html`<script>
const foo = {
bar() {
return 'bar';
},
__workletObject: true,
};
</script>`;

const { code } = runPlugin(input);
expect(code).not.toMatch(/__workletObject:\s*/g);
expect(code).toMatchSnapshot();
});

it('creates factories', () => {
const input = html`<script>
const foo = {
bar() {
return 'bar';
},
__workletObject: true,
};
</script>`;

const { code } = runPlugin(input);
expect(code).toContain('__workletObjectFactory');
expect(code).toHaveWorkletData();
expect(code).toMatchSnapshot();
});

it('workletizes regardless of marker value', () => {
const input = html`<script>
const foo = {
bar() {
return 'bar';
},
__workletObject: new RegExp('foo'),
};
</script>`;

const { code } = runPlugin(input);
expect(code).toHaveWorkletData();
expect(code).toMatchSnapshot();
});

it('preserves bindings', () => {
const input = html`<script>
const foo = {
bar() {
return 'bar';
},
foobar() {
return this.bar();
},
__workletObject: true,
};
</script>`;

const { code } = runPlugin(input);
expect(code).toIncludeInWorkletString('this.bar()');
expect(code).toMatchSnapshot();
});
});
});
Loading