diff --git a/src/bindings/js/docs/code_examples.md b/src/bindings/js/docs/code_examples.md new file mode 100644 index 00000000000000..13bfa14812d54b --- /dev/null +++ b/src/bindings/js/docs/code_examples.md @@ -0,0 +1,140 @@ +# How to extend the OpenVINO™ JavaScript API code + +## Build the OpenVINO™ JavaScript API +For detailed build instructions, refer to the [OpenVINO™ JavaScript API documentation](./README.md). + +## Project's naming conventions +When implementing the C++ sources for the JavaScript API, it is essential to adhere to the OpenVINO naming conventions described in the [OpenVINO Coding Style Guide](../../../../docs/dev/coding_style.md). In summary, the naming style employs `Snake Case` for methods, functions, and variables, while `Camel Case` is used for class names. Additionally, the naming of entities in the C++ sources should closely mirror their equivalents in the C++ API to maintain consistency. + +For methods that are exposed to JavaScript, the naming convention transitions to `Camel Case`, aligning with common JavaScript practices. As an example, a method in the C++ API named `get_element_type` would be represented in the JavaScript API as `getElementType()`. + +## node-addon-api module + +[node addon api](https://github.com/nodejs/node-addon-api) is used to create OpenVINO JavaScript API for Node.js. The quickest way to learn is to follow the official [examples](https://github.com/nodejs/node-addon-examples). It is recommended to check out the tutorial on [how to create a JavaScript object from a C++ object](https://github.com/nodejs/node-addon-examples/tree/main/src/2-js-to-native-conversion/object-wrap-demo/node-addon-api). + + + + + +## Adding a new class and method +To introduce a new `MyTensor` class that interacts with the `ov::Tensor` class, follow these steps: + - The class should facilitate construction from an ov::Tensor instance and allow initialization from a JavaScript element type and shape. + - It should also provide a getElementType method that retrieves the ov::Tensor element type. + +Begin by creating a header file for the `MyTensor` class in the OpenVINO repository at `/src/bindings/js/node/include/my_tensor.hpp`. This file should contain the necessary includes and class definitions: +```cpp +class MyTensor : public Napi::ObjectWrap { +public: + // Constructor for the wrapper class + MyTensor(const Napi::CallbackInfo& info); + + // It returns a JavaScript class definition + static Napi::Function get_class(Napi::Env env); + + // It returns the element type of ov::Tensor + Napi::Value get_element_type(const Napi::CallbackInfo& info); + +private: + ov::Tensor _tensor; +}; +``` +The implementation of the class methods should be placed in a source file at `/src/bindings/js/node/src/my_tensor.cpp` +```cpp +MyTensor::MyTensor(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { + std::vector allowed_signatures; + + try { + if (ov::js::validate(info, allowed_signatures)) { + const auto type = js_to_cpp(info, 0); + const auto& shape = js_to_cpp(info, 1); + this->_tensor = ov::Tensor(type, shape); + } else { + OPENVINO_THROW("'MyTensor' constructor", ov::js::get_parameters_error_msg(info, allowed_signatures)); + } + } catch (std::exception& err) { + reportError(info.Env(), err.what()); + } +} + +Napi::Function MyTensor::get_class(Napi::Env env) { + return DefineClass(env, + "MyTensor", + { + InstanceMethod("getElementType", &MyTensor::get_element_type), + }); +} + +Napi::Value MyTensor::get_element_type(const Napi::CallbackInfo& info) { + return Napi::String::New(info.Env(), _tensor.get_element_type().to_string()); +} +``` +Finally, update the `CMakeLists.txt` file at `/src/bindings/js/node/` to include the new source file in the build process: +```cmake +add_library(${PROJECT_NAME} SHARED + ${CMAKE_CURRENT_SOURCE_DIR}/src/my_tensor.cpp +) +``` + +### Argument validation and conversion + +When binding JavaScript arguments with C++ functions, it is crucial to validate and convert the arguments appropriately. The template `ov::js::validate` function is a utility that facilitates this process. It is particularly useful for handling different overloads of functions and ensuring standardized error messages when arguments do not match expected signatures. +Before implementing a new conversion function, such as `js_to_cpp`, review the existing [helper methods](../../node/include/helper.hpp) to see if one already meets your requirements. + +### New class initialization +When a new class is introduced to the `openvino-node` module, it must be initialized upon module loading. This is done in the [addon.cpp](../../src/addon.cpp) file. The initialization process registers the class with the Node.js environment so that it can be used within JavaScript code. +```cpp +Napi::Object init_module(Napi::Env env, Napi::Object exports) { + auto addon_data = new AddonData(); + env.SetInstanceData(addon_data); + init_class(env, exports, "MyTensor", &MyTensor::get_class, addon_data->my_tensor); + + return exports; +} +``` +To keep track of the JavaScript class definitions, they are kept in `/src/bindings/js/node/include/addon.hpp`. +```cpp +struct AddonData { + Napi::FunctionReference my_tensor; + // and other class references +}; +``` + +### Document the new functionality +The last step is to add the TypeScript type definitions and describe the new functionality. +```typescript +/** + * The {@link MyTensor} class and its documentation. + */ +interface MyTensor { + /** + * It gets the tensor element type. + */ + getElementType(): element; + +} +interface MyTensorConstructor { + /** + * It constructs a tensor using the element type and shape. The new tensor + * data will be allocated by default. + * @param type The element type of the new tensor. + * @param shape The shape of the new tensor. + */ + new(type: element | elementTypeString, shape: number[]): MyTensor; +} + +export interface NodeAddon { + MyTensor: MyTensorConstructor, +} +``` + + +## Testing the new code + +Now that coding is finished, remember to rebuild the project and test it out. + +To learn how to test your code, refer to the guide on [how to test OpenVINO™ JavaScript API.](./test_examples.md) + +## See also + * [OpenVINO™ README](../../../../README.md) + * [OpenVINO™ bindings README](../../README.md) + * [Developer documentation](../../../../docs/dev/index.md) \ No newline at end of file diff --git a/src/bindings/js/docs/test_examples.md b/src/bindings/js/docs/test_examples.md new file mode 100644 index 00000000000000..b8ff0c8ff7c9d0 --- /dev/null +++ b/src/bindings/js/docs/test_examples.md @@ -0,0 +1,87 @@ +# How to test the OpenVINO™ JavaScript API + +## Build the OpenVINO™ JavaScript API +For detailed build instructions, refer to the [OpenVINO™ JavaScript API documentation](./README.md). + + +## Run OpenVINO™ JavaScript API tests +*For simplicity, begin by navigating to the [main JavaScript API folder](./../node):* +```shell +cd /src/bindings/js/node +``` + +Use this command to run OpenVINO JavaScript API tests: +```shell +npm run test +``` + +To run specific test files, you can pass one or more glob patterns: +```shell +node --test "tests/unit/core.test.js" "tests/unit/*model.test.js" +``` + +Before executing individual test files, a one-time setup is required. If you have not previously executed `npm run test`, initiate the setup by running the following command: + +```shell +npm run test_setup +``` + +More information on running tests from the command line can be found in the [Node.js documentation]( https://nodejs.org/docs/latest/api/test.html#running-tests-from-the-command-line). + + +## Check the code style of the JavaScript API +*ESLint* is a tool to enforce a consistent coding style and to identify and fix potential issues in JavaScript code. + +To check the code style of the JavaScript API, run the following commands: +```shell +npm run lint +``` +*ESLint* can automatically fix many of the issues it detects. Use following command to run *ESLint* with the fix option on a single file: +```shell +npx eslint --fix "tests/unit/core.test.js" +``` + +It is recommended to run the code style check each time new tests are added. + + +## Writing OpenVINO™ JavaScript API tests +### Before start +Follow and complete [Examples of OpenVINO™ JavaScript API code](./code_examples.md). + + + +### Adding new test-case in the correct place +Each new test should verify the correct behavior of the new functionality (e.g. class, method). + +Unit test files are located in the `/src/bindings/js/node/tests/unit/` directory and their names correspond to the class/module to be tested. + +Always add tests to the correct locations and create new files only when necessary. *Remember to include the license on top of each new file*. + +### Test writing guidelines +Each test file starts with a `describe` block to group all tests related to a specific class or module. The name of the `describe` block should match the name of the class or module being tested, for example *ov.Core tests*. + +Within the `describe` block, individual tests are defined using `test` or `it` blocks, with the name of the test reflecting what is being tested. If multiple tests relate to the same method, they can be grouped within a nested `describe` block. + + ```js + const { describe, it, beforeEach } = require('node:test'); +describe('ov.Tensor tests', () => { + test('MyTensor.getElementType()', () => { + const tensor = new ov.MyTensor(ov.element.f32, [1, 3]); + assert.strictEqual(tensor.getElementType(), ov.element.f32); + }); + +}); + ``` +When writing tests, keep the following best practices in mind: + + * Focus on testing general usage and edge cases, but avoid excessive testing that leads to redundant test cases and can slow down validation pipelines. + * Avoid initializing shared variables outside of `test` or `beforeEach` blocks to prevent tests from affecting each other. + * Do not test built-in language features; instead, focus on the functionality provided by your code. + * Use hardcoded expected results to verify the correctness of your code. Alternatively, generate reference values at runtime using reliable libraries or methods. + * Extract and reuse common code snippets, such as helper functions or setup code, to improve test readability and maintainability. + + +## See also + * [OpenVINO™ README](../../../../README.md) + * [OpenVINO™ bindings README](../../README.md) + * [Developer documentation](../../../../docs/dev/index.md)