Skip to content

test: increase coverage #11

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

Merged
merged 16 commits into from
Mar 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .deepsource.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
version = 1

test_patterns = ["**/test/**"]
test_patterns = ["test/**"]

[[analyzers]]
name = "test-coverage"
enabled = true

[[analyzers]]
name = "secrets"
Expand Down
20 changes: 15 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ jobs:
node-version: ${{ matrix.node }}

- name: checkout
uses: actions/checkout@master
uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.sha }}

- name: Get yarn cache directory path
id: yarn-cache-dir-path
Expand All @@ -43,8 +45,16 @@ jobs:
- name: Lint
run: yarn lint

# - name: Test
# run: yarn test
- name: Test
run: yarn test

- name: Report test coverage results to DeepSource
run: |
# Install deepsource CLI
curl https://deepsource.io/cli | sh

# From the root directory, run the report coverage command
./bin/deepsource report --analyzer test-coverage --key javascript --value-file ./coverage/cobertura-coverage.xml

# - name: Coverage
# uses: codecov/codecov-action@v1
env:
DEEPSOURCE_DSN: ${{ secrets.DEEPSOURCE_DSN }}
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ Closes the WebSocket connection, optionally using code as the the WebSocket conn
this.$socketManager.close();
```

> The [onmessage](https://github.com/deepsourcelabs/nuxt-websocket/blob/main/src/templates/WebSocketManager.ts#L38-L45) handler expects data received from the server as either a string or an object of the shape `{event: string, data: string}`.
> The [onmessage](https://github.com/deepsourcelabs/nuxt-websocket/blob/main/src/templates/WebSocketManager.ts#L38-L45) handler expects data received from the server as either a string or an object of the shape `{ event: string, data: string }`.

```js
// Data received of the type string.
Expand All @@ -143,7 +143,7 @@ this.$socket.on("message", () => {});

// Data received as an object.
// Emits an event based on the value for the 'event' key.
// {event: "socket", data: "Hello world"}
// { event: "socket", data: "Hello world" }
this.$socket.on("socket", () => {});
```

Expand Down
3 changes: 2 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ module.exports = {
npm_package: '<rootDir>/src'
},
collectCoverage: true,
collectCoverageFrom: ['src/**']
collectCoverageFrom: ['src/**'],
coverageReporters: ['cobertura']
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"babel-jest": "latest",
"eslint": "latest",
"jest": "latest",
"jest-websocket-mock": "^2.3.0",
"nuxt-edge": "latest",
"playwright": "latest",
"siroc": "latest",
Expand Down
1 change: 1 addition & 0 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface ModuleOptions {
const CONFIG_KEY = 'websocket'

const websocketModule: Module<ModuleOptions> = function (moduleOptions) {
/* istanbul ignore next */
const options = Object.assign(this.options[CONFIG_KEY] || {}, moduleOptions)

const templatePath = join('src', 'templates')
Expand Down
7 changes: 5 additions & 2 deletions src/templates/WebSocketManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,28 @@ export default class WebSocketManager {
* @returns {void} Returns with no return value once the connection is established.
*/
connect (): void {
/* istanbul ignore next */
this.reconnectInterval = this.reconnectInterval || 1000
this.ws = new WebSocket(this.url)

this.ws.onmessage = (message) => {
try {
const data = JSON.parse(message.data)
this.emitter.$emit(data.event, data.data)
const { event, data } = JSON.parse(message.data)
this.emitter.$emit(event, data)
} catch (err) {
this.emitter.$emit('message', message)
}
}

this.ws.onclose = (event) => {
/* istanbul ignore next */
if (event) {
// 1000 is the normal close event.
if (event.code !== 1000) {
const maxReconnectInterval = 3000
setTimeout(() => {
// Reconnect interval can't be > x seconds.
/* istanbul ignore next */
if (this.reconnectInterval < maxReconnectInterval) {
this.reconnectInterval += 1000
}
Expand Down
7 changes: 4 additions & 3 deletions src/templates/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ const urlForProdFromOptions = '<%= options.urlForProd %>'
const urlForDevFromOptions = '<%= options.urlForDev %>'

export default ({ app }: { app: NuxtAppOptions }, inject: Inject): void => {
/* istanbul ignore next */
const url =
process.env.NODE_ENV === 'development'
? app.$config.webSocketUrlForDev ||
process.env.NODE_ENV === 'development'
? app.$config.webSocketUrlForDev ||
urlForDevFromOptions ||
'wss://echo.websocket.events/'
: app.$config.webSocketUrlForProd || urlForProdFromOptions
: app.$config.webSocketUrlForProd || urlForProdFromOptions

const emitter = new Vue()
const manager = new WebSocketManager(url, emitter, reconnectInterval)
Expand Down
150 changes: 150 additions & 0 deletions test/WebSocketManager.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import Vue from 'vue'
import WS from 'jest-websocket-mock'
import WebSocketManager from '../src/templates/WebSocketManager'

let server = {} as WS
let client = {} as WebSocketManager

describe('WebSocketManager', () => {
beforeEach(async () => {
const emitter = new Vue()

// Create a WS instance, listening on port 8025 on localhost.
server = new WS('ws://localhost:8025')

// Connect to the mock websocket server.
client = new WebSocketManager('ws://localhost:8025', emitter, 1000)

// Wait for the server to have established the connection.
await server.connected
})

afterEach(() => {
jest.restoreAllMocks()
WS.clean()
})

test('connect method establishes websocket connection', () => {
// connect method is invoked by the constructor.

// Assertions
expect(client.url).toBe('ws://localhost:8025')
expect(client.emitter).toBeInstanceOf(Vue)
expect(client.reconnectInterval).toBe(1000)
expect(client.ws).toBeInstanceOf(WebSocket)
})

test('emits an event of the type `message` on receiving the data as a string', () => {
// Mock
Vue.prototype.$emit = jest.fn()

// Send data to the client.
server.send('Test message')

// Assertions
expect(Vue.prototype.$emit.mock.calls[0][0]).toBe('message')
expect(Vue.prototype.$emit.mock.calls[0][1].data).toBe('Test message')
})

test('emits an event of the type based on value for the event key when the data is an object', () => {
// Mock
Vue.prototype.$emit = jest.fn()

// Send data to the client.
server.send(JSON.stringify({ event: 'socket', data: 'Hello world' }))

// Assertions
expect(Vue.prototype.$emit.mock.calls[0][0]).toBe('socket')
expect(Vue.prototype.$emit.mock.calls[0][1]).toBe('Hello world')
})

test('attempts reconnection on a close event which is not normal', async () => {
// Mocks
client.connect = jest.fn()
jest.spyOn(global, 'setTimeout')

// Close the connection with a code that is not normal.
server.close({ wasClean: false, code: 1003, reason: 'nope' })

// Assertions
expect(global.setTimeout).toBeCalledWith(expect.any(Function), 1000)

// Wait for 1s and assert for the reconnection attempt.
await new Promise(resolve => setTimeout(resolve, 1000))
expect(client.connect).toBeCalled()
})

test('closes the websocket connection on error event', () => {
// Mocks
console.error = jest.fn(); // eslint-disable-line
client.ws.close = jest.fn()

// Simulate an error and close the connection.
server.error()

// Assertions
expect(console.error).toBeCalled(); // eslint-disable-line
expect(client.ws.close).toBeCalled()
})

test('ready method waits for the open event if the readyState is not 1', () => {
// Assertion
expect(client.ready()).resolves.toBe(undefined)
})

test('ready method is resolved straightaway if the readyState is 1', () => {
Object.defineProperty(client.ws, 'readyState', {
value: 1
})

// Assertion
expect(client.ready()).resolves.toBe(undefined)
})

test('send method transmits the data received as a string', async () => {
// Mock implementations of other function calls.
jest.spyOn(client, 'ready').mockResolvedValue(Promise.resolve())
jest.spyOn(client.ws, 'send').mockReturnValue(undefined)

// Invoke send method with the message as a string.
await client.send('Hello world')

// Assertions
expect(client.ready).toBeCalled()
expect(client.ws.send).toBeCalledWith('Hello world')
})

test('send method transmits the data received as an object', async () => {
// Mock implementations of other function calls.
jest.spyOn(client, 'ready').mockResolvedValue(Promise.resolve())
jest.spyOn(client.ws, 'send').mockReturnValue(undefined)

// Invoke send method with the message as an object.
const msg = {
type: 'message',
text: 'Hello world',
id:
'id' +
Math.random()
.toString(16)
.slice(2),
date: Date.now()
}
await client.send(msg)

// Assertions
expect(client.ready).toBeCalled()
expect(client.ws.send).toBeCalledWith(JSON.stringify(msg))
})

test('close method closes the websocket connection', () => {
// Track WebSocket instance close method calls.
client.ws.close = jest.fn()

// Invoke close method.
client.close()

// Assertion
expect(client.ws.close).toBeCalled()
})
})
2 changes: 1 addition & 1 deletion test/browser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ describe('module', () => {
const page = await createPage('/')
const html = await page.content()

expect(html).toContain('Works!')
expect(html).toContain('Connection established!')
})
})
30 changes: 30 additions & 0 deletions test/plugin.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { NuxtAppOptions } from '@nuxt/types'
import { Inject } from '@nuxt/types/app'

import plugin from '../src/templates/plugin'

// Mocks
const ctx = {
app: {
$config: {
webSocketUrlForDev: 'wss://echo.websocket.events/'
}
} as NuxtAppOptions
}
const inject: jest.MockedFunction<Inject> = jest.fn()

describe('plugin', () => {
test('injects socket and socketManager plugins', () => {
Object.defineProperty(process.env, 'NODE_ENV', {
value: 'development'
})

// Invoke plugin function.
plugin(ctx, inject)

// Assertions
expect(inject).toBeCalledTimes(2)
expect(inject.mock.calls[0][0]).toBe('socket')
expect(inject.mock.calls[1][0]).toBe('socketManager')
})
})
Loading