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

feat(desktop): test support #1748

Merged
merged 2 commits into from
Sep 3, 2024
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
23 changes: 23 additions & 0 deletions .github/workflows/units_test_desktop.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: units test desktop

on:
pull_request:
release:
types:
- published

jobs:
desktop_test:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '16'

- name: Install dependencies and run unit tests
run: |
yarn install
yarn test:unit
53 changes: 53 additions & 0 deletions tests/unit/components/contextMenu.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { expect } from 'chai'
import { shallowMount, Wrapper } from '@vue/test-utils'
import Contextmenu from '@/components/Contextmenu.vue'
import { Vue } from 'vue-property-decorator'

describe('Contextmenu.vue', () => {
let wrapper: Wrapper<Vue>

beforeEach(() => {
wrapper = shallowMount(Contextmenu, {
propsData: {
top: 100,
left: 200,
visible: true,
},
stubs: ['el-card'],
})
})

afterEach(() => {
wrapper.destroy()
})

it('renders when visible', () => {
expect(wrapper.find('.contextmenu').exists()).to.be.true
})

it('does not render when not visible', async () => {
await wrapper.setProps({ visible: false })
expect(wrapper.find('.contextmenu').exists()).to.be.false
})

it('positions correctly based on props', () => {
const card = wrapper.find('.contextmenu')
expect(card.attributes('style')).to.include('top: 100px')
expect(card.attributes('style')).to.include('left: 200px')
})

it('emits update:visible event when clicked outside', async () => {
const vm = wrapper.vm as any
vm.handleClickoutside()
await Vue.nextTick()
expect(wrapper.emitted('update:visible')).to.deep.equal([[false]])
})

it('does not emit update:visible event when not visible', async () => {
await wrapper.setProps({ visible: false })
const vm = wrapper.vm as any
vm.handleClickoutside()
await Vue.nextTick()
expect(wrapper.emitted('update:visible')).to.be.undefined
})
})
62 changes: 27 additions & 35 deletions tests/unit/electron.spec.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,33 @@
// import testWithSpectron from 'vue-cli-plugin-electron-builder/lib/testWithSpectron'
// import chai from 'chai'
// import chaiAsPromised from 'chai-as-promised'
import testWithSpectron from 'vue-cli-plugin-electron-builder/lib/testWithSpectron'
import { expect } from 'chai'
import chaiAsPromised from 'chai-as-promised'

// chai.should()
// chai.use(chaiAsPromised)
chai.use(chaiAsPromised)

// describe('Application launch', function () {
// this.timeout(30000)
describe('Application launch', function () {
this.timeout(30000)

// beforeEach(function () {
// return testWithSpectron().then(instance => {
// this.app = instance.app
// this.stopServe = instance.stopServe
// })
// })
let spectron

// beforeEach(function () {
// chaiAsPromised.transferPromiseness = this.app.transferPromiseness
// })
beforeEach(async function () {
spectron = await testWithSpectron()
chaiAsPromised.transferPromiseness = spectron.app.transferPromiseness
})

// afterEach(function () {
// if (this.app && this.app.isRunning()) {
// return this.stopServe()
// }
// })
afterEach(async function () {
if (spectron && spectron.app && spectron.app.isRunning()) {
await spectron.stopServe()
}
})

// it('opens a window', function () {
// return this.app.client
// .getWindowCount()
// .should.eventually.have.at.least(1)
// .browserWindow.isMinimized()
// .should.eventually.be.false.browserWindow.isVisible()
// .should.eventually.be.true.browserWindow.getBounds()
// .should.eventually.have.property('width')
// .and.be.above(0)
// .browserWindow.getBounds()
// .should.eventually.have.property('height')
// .and.be.above(0)
// })
// })
it('opens a window', async function () {
const { app } = spectron
await expect(app.client.getWindowCount()).to.eventually.be.at.least(1)
const win = app.browserWindow
await expect(win.isMinimized()).to.eventually.be.false
await expect(win.isVisible()).to.eventually.be.true
const { width, height } = await win.getBounds()
expect(width).to.be.above(0)
expect(height).to.be.above(0)
})
})
13 changes: 0 additions & 13 deletions tests/unit/example.spec.ts

This file was deleted.

17 changes: 17 additions & 0 deletions tests/unit/utils/colors.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { expect } from 'chai'
import { defineColors, getRandomColor } from '@/utils/colors'

describe('colors utility functions', () => {
it('defineColors should have 5 predefined colors', () => {
expect(defineColors).to.be.an('array')
expect(defineColors).to.have.lengthOf(5)
defineColors.forEach((color) => {
expect(color).to.match(/^#[0-9A-F]{6}$/)
})
})

it('getRandomColor should return a valid hex color', () => {
const randomColor = getRandomColor()
expect(randomColor).to.match(/^#[0-9A-F]{6}$/)
})
})
58 changes: 58 additions & 0 deletions tests/unit/utils/data.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { expect } from 'chai'
import { isLargeData, calculateTextSize, LARGE_DATA_THRESHOLD, SHOW_MAX_LENGTH } from '@/utils/data'

describe('data utility functions', () => {
describe('LARGE_DATA_THRESHOLD', () => {
it('should be 524288', () => {
expect(LARGE_DATA_THRESHOLD).to.equal(524288)
})
})

describe('SHOW_MAX_LENGTH', () => {
it('should be 100', () => {
expect(SHOW_MAX_LENGTH).to.equal(100)
})
})

describe('isLargeData', () => {
it('should return false for small data', () => {
const smallData = 'a'.repeat(LARGE_DATA_THRESHOLD - 1)
expect(isLargeData(smallData)).to.be.false
})

it('should return true for large data', () => {
const largeData = 'a'.repeat(LARGE_DATA_THRESHOLD + 1)
expect(isLargeData(largeData)).to.be.true
})

it('should return false for data exactly at threshold', () => {
const thresholdData = 'a'.repeat(LARGE_DATA_THRESHOLD)
expect(isLargeData(thresholdData)).to.be.false
})
})

describe('calculateTextSize', () => {
it('should return "0 Bytes" for empty string', () => {
expect(calculateTextSize('')).to.equal('0 Bytes')
})

it('should calculate size in Bytes', () => {
expect(calculateTextSize('Hello')).to.equal('5 Bytes')
})

it('should calculate size in KB', () => {
const kilobyteString = 'a'.repeat(1024)
expect(calculateTextSize(kilobyteString)).to.equal('1 KB')
})

it('should calculate size in MB', () => {
const megabyteString = 'a'.repeat(1024 * 1024)
expect(calculateTextSize(megabyteString)).to.equal('1 MB')
})

it('should use specified number of decimal places', () => {
const string = 'a'.repeat(1500)
expect(calculateTextSize(string, 3)).to.equal('1.465 KB')
})
})
})
44 changes: 44 additions & 0 deletions tests/unit/utils/time.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { expect } from 'chai'
import moment from 'moment'
import { getNowDate, toFormat, convertSecondsToMs, sqliteDateFormat } from '@/utils/time'

describe('time utility functions', () => {
it('getNowDate should return current date in the specified format', () => {
const now = getNowDate()
expect(now).to.match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}:\d{3}$/)

const customFormat = 'YYYY-MM-DD'
const customNow = getNowDate(customFormat)
expect(customNow).to.match(/^\d{4}-\d{2}-\d{2}$/)
})

it('toFormat should convert date to sqliteDateFormat', () => {
const testDate = new Date('2023-05-15T12:30:45.678Z')
const formattedDate = toFormat(testDate)
expect(formattedDate).to.match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}:\d{3}$/)
})

it('convertSecondsToMs should correctly convert seconds to milliseconds', () => {
expect(convertSecondsToMs(1)).to.equal(1000)
expect(convertSecondsToMs(0.5)).to.equal(500)
expect(convertSecondsToMs(2.5)).to.equal(2500)
})

it('sqliteDateFormat should be the correct format string', () => {
expect(sqliteDateFormat).to.equal('YYYY-MM-DD HH:mm:ss:SSS')
})

it('getNowDate and toFormat should use the same format', () => {
const now = new Date()
const formattedNow = toFormat(now)
const getNowResult = getNowDate()

// Allow for a small time difference (up to 1 second) due to execution time
const momentNow = moment(now)
const momentFormatted = moment(formattedNow, sqliteDateFormat)
const momentGetNow = moment(getNowResult, sqliteDateFormat)

expect(Math.abs(momentNow.diff(momentFormatted, 'seconds'))).to.be.at.most(1)
expect(Math.abs(momentNow.diff(momentGetNow, 'seconds'))).to.be.at.most(1)
})
})
75 changes: 75 additions & 0 deletions tests/unit/utils/topicMatch.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { expect } from 'chai'
import topicMatch, { matchTopicMethod } from '@/utils/topicMatch'

describe('topicMatch utility', () => {
describe('matchTopicMethod', () => {
it('should match exact topics', () => {
expect(matchTopicMethod('a/b/c', 'a/b/c')).to.be.true
})

it('should not match different topics', () => {
expect(matchTopicMethod('a/b/c', 'a/b/d')).to.be.false
})

it('should match single-level wildcard', () => {
expect(matchTopicMethod('a/+/c', 'a/b/c')).to.be.true
expect(matchTopicMethod('a/+/c', 'a/d/c')).to.be.true
expect(matchTopicMethod('a/+/c', 'a/b/d')).to.be.false
})

it('should match multi-level wildcard', () => {
expect(matchTopicMethod('a/#', 'a/b/c')).to.be.true
expect(matchTopicMethod('a/#', 'a/d/e/f')).to.be.true
expect(matchTopicMethod('a/#', 'b/c/d')).to.be.false
})

it('should handle shared subscriptions', () => {
expect(matchTopicMethod('$share/group/a/+/c', 'a/b/c')).to.be.true
expect(matchTopicMethod('$share/group/a/#', 'a/b/c/d')).to.be.true
})
})

describe('topicMatch', () => {
const createMessage = (topic: string, payload: string): MessageModel => ({
topic,
payload,
createAt: new Date().toISOString(),
out: false,
qos: 0,
retain: false,
})

it('should filter messages based on topic', async () => {
const messages = [createMessage('a/b/c', '1'), createMessage('a/d/c', '2'), createMessage('x/y/z', '3')]

const result = await topicMatch(messages, 'a/+/c')
expect(result).to.have.lengthOf(2)
expect(result[0].payload).to.equal('1')
expect(result[1].payload).to.equal('2')
})

it('should return an empty array if no matches', async () => {
const messages = [createMessage('a/b/c', '1'), createMessage('a/d/c', '2')]

const result = await topicMatch(messages, 'x/y/z')
expect(result).to.be.an('array').that.is.empty
})

it('should handle multi-level wildcards correctly', async () => {
const messages = [createMessage('a/b/c', '1'), createMessage('a/d/e/f', '2'), createMessage('x/y/z', '3')]

const result = await topicMatch(messages, 'a/#')
expect(result).to.have.lengthOf(2)
expect(result[0].payload).to.equal('1')
expect(result[1].payload).to.equal('2')
})

it('should handle shared subscriptions', async () => {
const messages = [createMessage('a/b/c', '1'), createMessage('a/d/e', '2')]

const result = await topicMatch(messages, '$share/group/a/+/c')
expect(result).to.have.lengthOf(1)
expect(result[0].payload).to.equal('1')
})
})
})
Loading