Makes control.js testable with various config. changes. Adds coverage for util.js, roles.js, starts on control.js. Updates the entrypoint; untested so far

This commit is contained in:
Ben Menesini
2024-05-19 19:36:02 -07:00
parent e14c6f1685
commit db7338bcf3
11 changed files with 1483 additions and 767 deletions

1
__mocks__/fileMock.js Normal file
View File

@@ -0,0 +1 @@
module.exports = {};

View File

@@ -1,5 +1,7 @@
{ {
"presets": ["@babel/preset-env"], "presets": [
["@babel/preset-env", { "targets": { "node": "current" } }]
],
"plugins": [ "plugins": [
"@babel/plugin-proposal-optional-chaining", "@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-nullish-coalescing-operator", "@babel/plugin-proposal-nullish-coalescing-operator",
@@ -10,6 +12,7 @@
"pragma": "h", "pragma": "h",
"pragmaFrag": "Fragment" "pragmaFrag": "Fragment"
} }
] ],
["@babel/plugin-proposal-decorators", { "legacy": false, "decoratorsBeforeExport": true }]
] ]
} }

19
jest.config.js Normal file
View File

@@ -0,0 +1,19 @@
module.exports = {
verbose: true,
moduleFileExtensions: ['js', 'jsx', 'json', 'node'],
moduleNameMapper: {
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'<rootDir>/__mocks__/fileMock.js',
'\\.(css|less)$': 'identity-obj-proxy',
"^preact(/(.*)|$)": "preact$1"
},
testEnvironment: 'node',
transform: {
'^.+\\.jsx?$': 'babel-jest',
},
testPathIgnorePatterns: ['/node_modules/'],
coveragePathIgnorePatterns: ['/node_modules/'],
collectCoverage: true,
coverageReporters: ['json', 'lcov', 'text', 'clover'],
testEnvironment: 'jsdom'
};

2098
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -47,18 +47,21 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.21.4", "@babel/core": "^7.21.4",
"@babel/plugin-proposal-decorators": "^7.24.1",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
"@babel/plugin-proposal-optional-chaining": "^7.21.0", "@babel/plugin-proposal-optional-chaining": "^7.21.0",
"@babel/plugin-transform-react-jsx": "^7.21.0", "@babel/plugin-transform-react-jsx": "^7.21.0",
"@babel/preset-env": "^7.21.4", "@babel/preset-env": "^7.24.5",
"@svgr/webpack": "^7.0.0", "@svgr/webpack": "^7.0.0",
"babel-jest": "^29.5.0", "babel-jest": "^29.7.0",
"babel-loader": "^9.1.2", "babel-loader": "^9.1.2",
"babel-plugin-styled-components": "^2.1.1", "babel-plugin-styled-components": "^2.1.1",
"copy-webpack-plugin": "^11.0.0", "copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.7.3", "css-loader": "^6.7.3",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^29.5.0", "jest": "^29.5.0",
"jest-environment-jsdom": "^29.7.0",
"jest-junit": "^16.0.0", "jest-junit": "^16.0.0",
"prettier": "2.8.7", "prettier": "2.8.7",
"style-loader": "^3.3.2", "style-loader": "^3.3.2",

39
src/roles.test.js Normal file
View File

@@ -0,0 +1,39 @@
import { roleCan } from './roles.js';
describe('roleCan', () => {
it('should return true for admin role regardless of action', () => {
expect(roleCan('admin', 'any-action')).toBe(true);
});
it('should return true for operator role and valid action', () => {
expect(roleCan('operator', 'set-listening-view')).toBe(true);
});
it('should return false for operator role and invalid action', () => {
expect(roleCan('operator', 'invalid-action')).toBe(false);
});
it('should return false for operator role and un-granted action', () => {
expect(roleCan('operator', 'dev-tools')).toBe(false);
});
it('should return true for monitor role and valid action', () => {
expect(roleCan('monitor', 'set-view-blurred')).toBe(true);
});
it('should return false for monitor role and invalid action', () => {
expect(roleCan('monitor', 'invalid-action')).toBe(false);
});
it('should return false for monitor role and un-granted action', () => {
expect(roleCan('monitor', 'set-listening-view')).toBe(false);
});
it('should return false for invalid role regardless of action', () => {
expect(roleCan('invalid-role', 'any-action')).toBe(false);
});
it('should return false for invalid role and valid action', () => {
expect(roleCan('invalid-role', 'set-listening-view')).toBe(false);
});
});

17
src/util.test.js Normal file
View File

@@ -0,0 +1,17 @@
import { ensureValidURL } from './util'
describe('ensureValidURL', () => {
it('should not throw an error for valid http and https URLs', () => {
expect(() => ensureValidURL('http://example.com')).not.toThrow()
expect(() => ensureValidURL('https://example.com')).not.toThrow()
})
it('should throw an error for non-http and non-https URLs', () => {
expect(() => ensureValidURL('ftp://example.com')).toThrow()
expect(() => ensureValidURL('file://example.com')).toThrow()
})
it('should throw an error for invalid URLs', () => {
expect(() => ensureValidURL('invalid')).toThrow()
})
})

View File

@@ -1491,13 +1491,14 @@ const TIN = styled.div`
function main() { function main() {
const script = document.getElementById('main-script') const script = document.getElementById('main-script')
const wsEndpoint = typeof script?.dataset?.wsEndpoint === 'string' ? script.dataset.wsEndpoint : 'defaultWsEndpoint';
const role = typeof script?.dataset?.role === 'string' ? script.dataset.role : 'defaultRole';
render( render(
<> <>
<GlobalStyle /> <GlobalStyle />
<App wsEndpoint={script.dataset.wsEndpoint} role={script.dataset.role} /> <App wsEndpoint={wsEndpoint} role={role} />
</>, </>,
document.body, document.body,
) )
} }
main()

50
src/web/control.test.js Normal file
View File

@@ -0,0 +1,50 @@
import { filterStreams, useYDoc, useStreamwallConnection } from './control.js'
// import { renderHook, act } from '@testing-library/react-hooks'
describe("control test always passes", () => {
it("always passes", () => {
expect(true).toBe(true);
});
});
// describe('filterStreams', () => {
// it('should correctly filter live and other streams', () => {
// const streams = [
// { kind: 'video', status: 'Live' },
// { kind: 'audio', status: 'Offline' },
// { kind: 'video', status: 'Offline' },
// ]
// const [liveStreams, otherStreams] = filterStreams(streams)
// expect(liveStreams).toHaveLength(1)
// expect(otherStreams).toHaveLength(2)
// })
// })
// describe('useYDoc', () => {
// it('should initialize with an empty Y.Doc', () => {
// const { result } = renderHook(() => useYDoc(['test']))
// expect(result.current[0]).toEqual({})
// })
// it('should update docValue when doc is updated', () => {
// const { result } = renderHook(() => useYDoc(['test']))
// act(() => {
// result.current[1].getMap('test').set('key', 'value')
// })
// expect(result.current[0]).toEqual({ test: { key: 'value' } })
// })
// })
// describe('useStreamwallConnection', () => {
// it('should initialize with default values', () => {
// const { result } = renderHook(() => useStreamwallConnection('ws://localhost:8080'))
// expect(result.current.isConnected).toBe(false)
// expect(result.current.config).toEqual({})
// expect(result.current.streams).toEqual([])
// expect(result.current.customStreams).toEqual([])
// expect(result.current.views).toEqual([])
// expect(result.current.stateIdxMap).toEqual(new Map())
// expect(result.current.delayState).toBeUndefined()
// expect(result.current.authState).toBeUndefined()
// })
// })

3
src/web/entrypoint.js Normal file
View File

@@ -0,0 +1,3 @@
import { main } from './control.js';
main();

View File

@@ -108,7 +108,7 @@ const webConfig = {
devtool: 'cheap-source-map', devtool: 'cheap-source-map',
target: 'web', target: 'web',
entry: { entry: {
control: './src/web/control.js', control: './src/web/entrypoint.js',
}, },
output: { output: {
path: path.resolve(__dirname, 'dist/web'), path: path.resolve(__dirname, 'dist/web'),