{"id":49254612,"url":"https://github.com/wxsd-sales/jest-mock-xapi","last_synced_at":"2026-04-25T02:03:56.349Z","repository":{"id":352908462,"uuid":"1204800146","full_name":"wxsd-sales/jest-mock-xapi","owner":"wxsd-sales","description":"A jest mock xAPI module validating and testing Cisco Collaboration Device macros","archived":false,"fork":false,"pushed_at":"2026-04-21T16:10:26.000Z","size":207,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-21T18:14:36.704Z","etag":null,"topics":["macros","roomos","xapi"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wxsd-sales.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-08T10:48:37.000Z","updated_at":"2026-04-21T16:06:23.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/wxsd-sales/jest-mock-xapi","commit_stats":null,"previous_names":["wxsd-sales/jest-mock-xapi"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/wxsd-sales/jest-mock-xapi","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wxsd-sales%2Fjest-mock-xapi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wxsd-sales%2Fjest-mock-xapi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wxsd-sales%2Fjest-mock-xapi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wxsd-sales%2Fjest-mock-xapi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wxsd-sales","download_url":"https://codeload.github.com/wxsd-sales/jest-mock-xapi/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wxsd-sales%2Fjest-mock-xapi/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32247508,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-24T13:21:15.438Z","status":"online","status_checked_at":"2026-04-25T02:00:06.260Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["macros","roomos","xapi"],"created_at":"2026-04-25T02:03:53.118Z","updated_at":"2026-04-25T02:03:56.338Z","avatar_url":"https://github.com/wxsd-sales.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Jest Mock xAPI\n\nRun Cisco RoomOS JavaScript macro tests in Node.js while preserving the normal `import xapi from \"xapi\"` developer experience.\n\nThis project provides a Jest-compatible mock of the RoomOS `xapi` module so JavaScript macros for Cisco RoomOS devices can be tested locally in a standard Node environment. It exists so macro developers can validate macro behavior without having to deploy to a device for every change or lose the familiar RoomOS import pattern in their source files. It is intended for developers building and maintaining Cisco RoomOS macros who want repeatable automated tests around xAPI commands, status reads, configuration changes, and emitted events.\n\n\n## Overview\n\nThe package exposes a mocked `xapi` module that mirrors the top-level `Command`, `Status`, `Config`, and `Event` areas that RoomOS macro developers already use. Internally, it uses a schema-backed proxy so only valid xAPI paths are available, and each path resolves to a Jest mock function that can be inspected with normal Jest matchers. Commands resolve as promises so command handlers look like the real RoomOS async API, while status, configuration, and event paths support setting values, subscribing to changes, and emitting updates from tests. In practice, a macro test imports the macro, uses the mock xAPI to seed state or emit events, and then asserts that the macro called the expected xAPI command or updated the expected path in response.\n\n\n## Setup\n\n### Prerequisites \u0026 Dependencies: \n\n- Node.js 20 or later is recommended for local development and testing.\n- A Jest-based test setup is expected in the macro project that consumes this package.\n- The macro under test should import `xapi` exactly as it would on a Cisco RoomOS device: `import xapi from \"xapi\";`.\n- This package is intended for local macro testing and assumes the macro developer is comfortable writing JavaScript or TypeScript unit tests with Jest.\n\n\n\u003c!-- GETTING STARTED --\u003e\n\n### Installation Steps:\n1.  Install `jest-mock-xapi` and Jest in your macro project.\n    ```sh\n    npm install --save-dev jest jest-mock-xapi\n    ```\n2.  Choose one Jest integration option.\n\n    Option 1 (recommended): Map `xapi` directly to `jest-mock-xapi` with `moduleNameMapper`.\n    ```json\n    {\n      \"jest\": {\n        \"moduleNameMapper\": {\n          \"^xapi$\": \"jest-mock-xapi\"\n        }\n      }\n    }\n    ```\n\n    Option 2: Register the virtual `xapi` module through the package's setup entrypoint if you prefer a setup-file workflow.\n    ```json\n    {\n      \"jest\": {\n        \"setupFiles\": [\"jest-mock-xapi/register\"]\n      }\n    }\n    ```\n3.  Add Jest test scripts to your macro project's `package.json`.\n    ```json\n    {\n      \"scripts\": {\n        \"test\": \"NODE_OPTIONS=--experimental-vm-modules jest --runInBand\",\n        \"test:watch\": \"NODE_OPTIONS=--experimental-vm-modules jest --watchAll --runInBand\"\n      }\n    }\n    ```\n4.  With option 1 in place, write tests that import your macro, set xAPI values or emit xAPI changes, and then assert the macro responded correctly.\n    ```js\n    import { beforeEach, describe, expect, it, jest } from \"@jest/globals\";\n\n    describe(\"my roomos macro\", () =\u003e {\n      beforeEach(() =\u003e {\n        jest.resetModules();\n      });\n\n      it(\"dials when a panel event is triggered\", async () =\u003e {\n        const { default: xapi } = await import(\"xapi\");\n        jest.clearAllMocks();\n        xapi.removeAllListeners();\n\n        await import(\"./my-macro.js\");\n\n        xapi.Event.UserInterface.Extensions.Panel.Clicked.emit({\n          PanelId: \"speed-dial-panel\",\n        });\n\n        expect(xapi.Command.Dial).toHaveBeenCalledWith({\n          Number: \"number@example.com\",\n        });\n      });\n    });\n    ```\n5.  Run the tests from your macro project.\n\n    Run your tests once with:\n    ```sh\n    npm test\n    ```\n\n    Run your tests continuously upon macro/test code changes with:\n\n    ```sh\n    npm run test:watch\n    ```\n\nThe package now has a split API:\n\n- `jest-mock-xapi` exports the mock object itself and is the recommended target for `moduleNameMapper`.\n- `jest-mock-xapi/register` registers a virtual `xapi` module for Jest setup-file workflows.\n\n## Usage\n\nThe expected development flow is that a macro developer installs `jest-mock-xapi`, keeps production macro code written for the native RoomOS runtime, and uses Jest to control mock device state from tests. In practice, a test imports the macro, seeds status or config values, emits events or updates, and then asserts the macro reacted with the expected xAPI calls.\n\n### Reset state between tests\n\nMost test suites should clear mocks and listeners before each test so one scenario does not leak into the next.\n\n```js\nimport { beforeEach, jest } from \"@jest/globals\";\n\nbeforeEach(async () =\u003e {\n  jest.resetModules();\n  const { default: xapi } = await import(\"xapi\");\n  jest.clearAllMocks();\n  xapi.removeAllListeners();\n});\n```\n\n### Assert xCommand calls\n\nCommands are Jest mocks, so you can assert on them with normal Jest matchers.\n\n```js\nimport { expect, it } from \"@jest/globals\";\n\nit(\"dials the requested destination\", async () =\u003e {\n  const { default: xapi } = await import(\"xapi\");\n\n  await someMacroFunction();\n\n  expect(xapi.Command.Dial).toHaveBeenCalledWith({\n    Number: \"number@example.com\",\n  });\n});\n```\n\n### Use RoomOS runtime globals\n\nThe mock installs the RoomOS `_main_module_name()` global when `jest-mock-xapi` is loaded. It returns the name of the calling macro file without the source extension, matching the RoomOS behavior used by self-managing macros.\n\n```js\nimport xapi from \"xapi\";\n\nconst macroName = _main_module_name();\n\nxapi.Command.Macros.Macro.Deactivate({ Name: macroName });\n```\n\nFor example, calling `_main_module_name()` from `self-deactivating-macro.js` returns `\"self-deactivating-macro\"`.\n\n### Seed leaf status and config values\n\nUse `set()` to prepare mock device state before importing a macro or invoking a handler.\n\n```js\nimport { expect, it } from \"@jest/globals\";\n\nit(\"reads the prepared default volume\", async () =\u003e {\n  const { default: xapi } = await import(\"xapi\");\n\n  xapi.Config.Audio.DefaultVolume.set(40);\n  xapi.Status.Audio.Volume.set(20);\n\n  expect(xapi.Config.Audio.DefaultVolume.get()).toBe(40);\n  expect(xapi.Status.Audio.Volume.get()).toBe(20);\n});\n```\n\n### Select a RoomOS product\n\nSet `Status.SystemUnit.ProductPlatform` to a public product name when a test should enforce product-specific xAPI availability. Once set to a known product, the mock rejects xAPI paths that are not available on that product and validates product-specific configuration values.\n\n```js\nimport { expect, it } from \"@jest/globals\";\n\nit(\"handles Desk Pro xAPI differences\", async () =\u003e {\n  const { default: xapi } = await import(\"xapi\");\n\n  xapi.Status.SystemUnit.ProductPlatform.set(\"Desk Pro\");\n\n  xapi.Config.Video.Output.Connector[1].MonitorRole.set(\"Auto\");\n\n  await expect(\n    xapi.Config.Video.Output.Connector[3].MonitorRole.set(\"Auto\"),\n  ).rejects.toEqual({\n    code: -32602,\n    message: \"No match on Path argument\",\n  });\n\n  await expect(\n    xapi.Config.Video.Output.Connector[1].MonitorRole.set(\"PresentationOnly\"),\n  ).rejects.toEqual({\n    code: -32602,\n    message: \"Bad usage: Missing or invalid parameter(s).\",\n  });\n});\n```\n\nIf no known product platform has been set, the mock keeps the broad schema-backed behavior and does not apply product-specific filtering.\n\n### Read full status or config branches\n\nThe mock supports aggregate `get()` calls on root paths and indexed branches, similar to the real xAPI module.\n\n```js\nimport { expect, it } from \"@jest/globals\";\n\nit(\"returns full config branches\", async () =\u003e {\n  const { default: xapi } = await import(\"xapi\");\n\n  xapi.Config.Cameras.Camera[1].Brightness.Mode.set(\"Manual\");\n  xapi.Config.Cameras.Camera[2].Brightness.Mode.set(\"Auto\");\n\n  expect(xapi.Config.get()).toHaveProperty(\"Audio\");\n  expect(xapi.Config.Cameras.Camera[1].get()).toEqual({\n    Brightness: {\n      Mode: \"Manual\",\n    },\n  });\n  expect(xapi.Config.Cameras.Camera.get()).toEqual([\n    { Brightness: { Mode: \"Manual\" } },\n    { Brightness: { Mode: \"Auto\" } },\n  ]);\n  expect(xapi.Config.Cameras.Camera[\"*\"].get()).toEqual([\n    { Brightness: { Mode: \"Manual\" } },\n    { Brightness: { Mode: \"Auto\" } },\n  ]);\n});\n```\n\n### Emit xEvent payloads\n\nUse `emit()` on event paths to simulate the same payloads a RoomOS device would send to a macro.\n\n```js\nimport { expect, it } from \"@jest/globals\";\n\nit(\"reacts to a panel press\", async () =\u003e {\n  const { default: xapi } = await import(\"xapi\");\n\n  await import(\"./my-macro.js\");\n\n  xapi.Event.UserInterface.Extensions.Panel.Clicked.emit({\n    PanelId: \"speed-dial-panel\",\n  });\n\n  expect(xapi.Command.Dial).toHaveBeenCalledWith({\n    Number: \"number@example.com\",\n  });\n});\n```\n\n### Subscribe to leaf updates\n\nLeaf subscriptions behave like the real macro API and receive the updated value directly.\n\n```js\nimport { expect, it, jest } from \"@jest/globals\";\n\nit(\"notifies leaf status listeners\", async () =\u003e {\n  const { default: xapi } = await import(\"xapi\");\n  const handler = jest.fn();\n\n  xapi.Status.Audio.Volume.on(handler);\n  xapi.Status.Audio.Volume.set(55);\n\n  expect(handler).toHaveBeenCalledWith(55);\n});\n```\n\n### Subscribe to root or branch updates\n\nRoot and branch listeners receive a nested payload scoped to the changed branch.\n\n```js\nimport { expect, it, jest } from \"@jest/globals\";\n\nit(\"notifies root listeners with relative path payloads\", async () =\u003e {\n  const { default: xapi } = await import(\"xapi\");\n  const statusHandler = jest.fn();\n  const configHandler = jest.fn();\n  const eventHandler = jest.fn();\n\n  xapi.Status.on(statusHandler);\n  xapi.Config.on(configHandler);\n  xapi.Event.on(eventHandler);\n\n  xapi.Status.Audio.Volume.set(55);\n  xapi.Config.Audio.DefaultVolume.set(35);\n  xapi.Event.UserInterface.Extensions.Panel.Clicked.emit({\n    PanelId: \"speed-dial-panel\",\n  });\n\n  expect(statusHandler).toHaveBeenCalledWith({\n    Audio: {\n      Volume: 55,\n    },\n  });\n  expect(configHandler).toHaveBeenCalledWith({\n    Audio: {\n      DefaultVolume: 35,\n    },\n  });\n  expect(eventHandler).toHaveBeenCalledWith({\n    UserInterface: {\n      Extensions: {\n        Panel: {\n          Clicked: {\n            PanelId: \"speed-dial-panel\",\n          },\n        },\n      },\n    },\n  });\n});\n```\n\n### Track indexed status branches such as calls\n\nIndexed collection listeners such as `xapi.Status.Call.on(...)` receive the full branch snapshot plus the branch `id`.\n\n```js\nimport { expect, it, jest } from \"@jest/globals\";\n\nit(\"notifies call listeners as a call branch changes\", async () =\u003e {\n  const { default: xapi } = await import(\"xapi\");\n  const handler = jest.fn();\n\n  xapi.Status.Call.on(handler);\n  xapi.Status.Call[42].Direction.set(\"Outgoing\");\n  xapi.Status.Call[42].Status.set(\"Connected\");\n\n  expect(handler).toHaveBeenNthCalledWith(1, {\n    Direction: \"Outgoing\",\n    id: \"42\",\n  });\n  expect(handler).toHaveBeenNthCalledWith(2, {\n    Direction: \"Outgoing\",\n    Status: \"Connected\",\n    id: \"42\",\n  });\n});\n```\n\n### Remove indexed status branches\n\nUse `removeStatus()` to simulate an indexed status branch disappearing, such as a call ending.\n\n```js\nimport { expect, it, jest } from \"@jest/globals\";\n\nit(\"emits a ghost payload when a call ends\", async () =\u003e {\n  const { default: xapi } = await import(\"xapi\");\n  const handler = jest.fn();\n\n  xapi.Status.Call.on(handler);\n  xapi.Status.Call[7].Direction.set(\"Incoming\");\n\n  xapi.removeStatus(\"Call.7\");\n\n  expect(handler).toHaveBeenLastCalledWith({\n    ghost: \"true\",\n    id: \"7\",\n  });\n});\n```\n\n## Demo\n\nFor complete examples, see the [speed-dial-macro demo](./examples/speed-dial-macro/README.md), the [self-deactivating-macro demo](./examples/self-deactivating-macro/README.md), and the [monitor-role-changer demo](./examples/monitor-role-changer/README.md).\n\n*For more demos \u0026 PoCs like this, check out our [Webex Labs site](https://collabtoolbox.cisco.com/webex-labs).\n\n\n\n## License\n\nAll contents are licensed under the MIT license. Please see [license](LICENSE) for details.\n\n\n## Disclaimer\n\nEverything included is for demo and Proof of Concept purposes only. Use of the site is solely at your own risk. This site may contain links to third party content, which we do not warrant, endorse, or assume liability for. These demos are for Cisco Webex usecases, but are not Official Cisco Webex Branded demos.\n\n\n## Questions\nPlease contact the WXSD team at [wxsd@external.cisco.com](mailto:wxsd@external.cisco.com?subject=jest-mock-xapi) for questions. Or, if you're a Cisco internal employee, reach out to us on the Webex App via our bot (globalexpert@webex.bot). In the \"Engagement Type\" field, choose the \"API/SDK Proof of Concept Integration Development\" option to make sure you reach our team. \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwxsd-sales%2Fjest-mock-xapi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwxsd-sales%2Fjest-mock-xapi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwxsd-sales%2Fjest-mock-xapi/lists"}