Initial Commit
This commit is contained in:
175
.gitignore
vendored
Normal file
175
.gitignore
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
|
||||
|
||||
# Logs
|
||||
|
||||
logs
|
||||
_.log
|
||||
npm-debug.log_
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Caches
|
||||
|
||||
.cache
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# Runtime data
|
||||
|
||||
pids
|
||||
_.pid
|
||||
_.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
|
||||
.temp
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
# IntelliJ based IDEs
|
||||
.idea
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
2
.npmrc
Normal file
2
.npmrc
Normal file
@@ -0,0 +1,2 @@
|
||||
@techniker-me:registry=https://registry-node.techniker.me
|
||||
//registry-node.techniker.me/:_authToken="${NODE_REGISTRY_AUTH_TOKEN}"
|
||||
12
.prettierrc
Normal file
12
.prettierrc
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"arrowParens": "avoid",
|
||||
"bracketSameLine": true,
|
||||
"bracketSpacing": false,
|
||||
"printWidth": 160,
|
||||
"semi": true,
|
||||
"singleAttributePerLine": false,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "none",
|
||||
"useTabs": false
|
||||
}
|
||||
27
README
Normal file
27
README
Normal file
@@ -0,0 +1,27 @@
|
||||
# @zinntechniker/tools
|
||||
|
||||
A library of useful tools
|
||||
|
||||
### Assertions
|
||||
|
||||
- Assert unreachable
|
||||
|
||||
### Disposables
|
||||
|
||||
- Disposable
|
||||
- DisposabeList
|
||||
|
||||
### Events
|
||||
|
||||
- EventEmitter
|
||||
- EventPublisher
|
||||
|
||||
### Maths
|
||||
|
||||
- Averager
|
||||
- Random
|
||||
|
||||
### Observables
|
||||
|
||||
- Subject
|
||||
- ReadOnlySubject
|
||||
11
bunfig.toml
Normal file
11
bunfig.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
telemetry = false
|
||||
|
||||
[install]
|
||||
exact = true
|
||||
|
||||
[install.lockfile]
|
||||
save = false
|
||||
|
||||
[test]
|
||||
coverage = true
|
||||
coverageSkipTestFiles = true
|
||||
41
eslint.config.js
Normal file
41
eslint.config.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import globals from 'globals';
|
||||
import pluginJs from '@eslint/js';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
export default [
|
||||
{
|
||||
...tseslint.configs.recommended,
|
||||
...pluginJs.configs.recommended,
|
||||
ignores: [
|
||||
// Existing ignores
|
||||
'./test/',
|
||||
'node_modules/**', // Explicitly ignore node_modules and subdirs
|
||||
// Add Bun-specific cache ignores (adjust paths if needed)
|
||||
'**/.bun/**', // Covers Bun's cache dirs
|
||||
'/home/teamcity-agent/.bun/**', // Absolute path for CI (if known; customize for your agent)
|
||||
// Other common ignores
|
||||
'**/dist/**', // Build outputs
|
||||
'**/build/**',
|
||||
'**/temp/**',
|
||||
'**/cache/**',
|
||||
'**/*.min.js', // Minified files
|
||||
],
|
||||
files: ['./src/**/*.{ts,js,tsx,jsx}'], // Expanded to include JSX if needed; keeps it scoped to src
|
||||
languageOptions: {
|
||||
globals: { ...globals.browser, ...globals.node },
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest', // Ensure modern JS support
|
||||
sourceType: 'module',
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
// Optional: Suppress specific warnings seen in log (e.g., unused disables)
|
||||
'no-unused-vars': 'warn', // Downgrade if needed
|
||||
// Add rules to handle common log issues (customize as needed)
|
||||
'@typescript-eslint/no-non-null-assertion': 'off', // Temporarily disable if causing many errors
|
||||
'@typescript-eslint/no-unsafe-enum-comparison': 'off',
|
||||
// Ignore undefined rules in third-party code
|
||||
'eslint-plugin/no-property-in-node': 'off',
|
||||
},
|
||||
},
|
||||
];
|
||||
175
examples/.gitignore
vendored
Normal file
175
examples/.gitignore
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
|
||||
|
||||
# Logs
|
||||
|
||||
logs
|
||||
_.log
|
||||
npm-debug.log_
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Caches
|
||||
|
||||
.cache
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# Runtime data
|
||||
|
||||
pids
|
||||
_.pid
|
||||
_.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
|
||||
.temp
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
# IntelliJ based IDEs
|
||||
.idea
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
15
examples/README.md
Normal file
15
examples/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# examples
|
||||
|
||||
To install dependencies:
|
||||
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
To run:
|
||||
|
||||
```bash
|
||||
bun run index.ts
|
||||
```
|
||||
|
||||
This project was created using `bun init` in bun v1.1.20. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
|
||||
BIN
examples/bun.lockb
Executable file
BIN
examples/bun.lockb
Executable file
Binary file not shown.
11
examples/bunfig.toml
Normal file
11
examples/bunfig.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
telemetry = false
|
||||
|
||||
[install]
|
||||
exact = true
|
||||
|
||||
[install.lockfile]
|
||||
save = false
|
||||
|
||||
[test]
|
||||
coverage = true
|
||||
coverageSkipTestFiles = true
|
||||
17
examples/package.json
Normal file
17
examples/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "@zinntechniker/tools-examples",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"author": {
|
||||
"name": "Alexander Zinn",
|
||||
"git+url": "https://github.com/zinntechniker/tools"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"@zinntechniker/tools": "latest",
|
||||
"typescript": "5.5.2"
|
||||
},
|
||||
"publishConfig": {
|
||||
"registry": "https://registry-node.techniker.me"
|
||||
}
|
||||
}
|
||||
13
examples/src/example-observables.ts
Normal file
13
examples/src/example-observables.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import Tools from '@zinntechniker/tools';
|
||||
|
||||
const {
|
||||
observables: {Subject, ReadOnlySubject}
|
||||
} = Tools;
|
||||
|
||||
const subject = new Subject<number>(0);
|
||||
const subjectSubscriber = (value: number) => {
|
||||
console.log("Subject's value changed to [%o]", value);
|
||||
};
|
||||
subject.subscribe(subjectSubscriber);
|
||||
|
||||
subject.value = 12;
|
||||
28
examples/tsconfig.json
Normal file
28
examples/tsconfig.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Enable latest features
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"allowJs": true,
|
||||
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
// Best practices
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["./dist", "./test"]
|
||||
}
|
||||
56
package.json
Normal file
56
package.json
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "@techniker-me/tools",
|
||||
"version": "2025.0.16",
|
||||
"type": "module",
|
||||
"private": false,
|
||||
"types": "./dist/types/index.d.ts",
|
||||
"exports": {
|
||||
"types": "./dist/types/index.d.ts",
|
||||
"import": "./dist/node/index.js",
|
||||
"browser": "./dist/browser/index.js",
|
||||
"default": "./dist/node/index.js"
|
||||
},
|
||||
"author": {
|
||||
"name": "Alexander Zinn",
|
||||
"git+url": "https://git.techniker.me/techniker-me/tools.git"
|
||||
},
|
||||
"publishConfig": {
|
||||
"registry": "http://localhost:7873"
|
||||
},
|
||||
"scripts": {
|
||||
"ci-install": "bun install",
|
||||
"ci-test": "bun test",
|
||||
"ci-build": "bash scripts/ci-build.sh",
|
||||
"ci-deploy:ga": "bash scripts/ci-deploy.sh --beta false",
|
||||
"ci-deploy:beta": "bash scripts/ci-deploy.sh --beta true",
|
||||
"lint": "eslint",
|
||||
"lint:fix": "eslint --fix",
|
||||
"test": "bun test",
|
||||
"build:node:debug": "bun build ./src/index.ts --target=node --sourcemap=none --format=esm --sourcemap=inline --outdir=dist/node",
|
||||
"build:browser:debug": "bun build ./src/index.ts --target=browser --sourcemap=none --format=esm --sourcemap=inline --outdir=dist/browser",
|
||||
"build:node": "bun build ./src/index.ts --target=node --sourcemap=none --format=esm --splitting --minify --outdir=dist/node",
|
||||
"build:browser": "bun build ./src/index.ts --target=browser --sourcemap=none --format=esm --splitting --minify --outdir=dist/browser",
|
||||
"build:types": "bunx tsc -p tsconfig.d.json",
|
||||
"build:prepare-package-json": "bash scripts/prepare-package-json.sh"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "8.57.1",
|
||||
"@types/bun": "latest",
|
||||
"@types/node": "22.5.2",
|
||||
"@typescript-eslint/eslint-plugin": "8.37.0",
|
||||
"@typescript-eslint/parser": "8.37.0",
|
||||
"eslint": "8.57.1",
|
||||
"globals": "15.9.0",
|
||||
"prettier": "3.3.3",
|
||||
"typescript": "5.5.4",
|
||||
"typescript-eslint": "8.4.0"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"browser",
|
||||
"node",
|
||||
"types",
|
||||
"package.json",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
29
scripts/ci-build.sh
Executable file
29
scripts/ci-build.sh
Executable file
@@ -0,0 +1,29 @@
|
||||
#! /usr/bin/env bash
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
distDirectory=${DIST_DIRECTORY:-"dist"}
|
||||
|
||||
rm -rf ${distDirectory}
|
||||
|
||||
bun run lint
|
||||
bun run build:node
|
||||
bun run build:browser
|
||||
bun run build:types
|
||||
bun run build:prepare-package-json
|
||||
|
||||
echo "Copying [.npmrc] to [${distDirectory}]"
|
||||
cp .npmrc ./${distDirectory}
|
||||
|
||||
echo "Copying [.nvmrc] to [${distDirectory}]"
|
||||
cp .nvmrc ./${distDirectory}
|
||||
|
||||
echo "Copying [README.md] to [${distDirectory}]"
|
||||
cp README ./${distDirectory}
|
||||
|
||||
ls ${distDirectory}
|
||||
|
||||
echo -e "\nci-build complete!"
|
||||
exit 0
|
||||
83
scripts/ci-deploy.sh
Executable file
83
scripts/ci-deploy.sh
Executable file
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
|
||||
registryUrl="http://localhost:4873"
|
||||
# registryUrl="https://registry-node.techniker.me"
|
||||
packageVersionToDeploy=""
|
||||
isBeta="false"
|
||||
|
||||
while [[ "${#}" -gt 0 ]]; do
|
||||
case "${1}" in
|
||||
--beta)
|
||||
isBeta="${2}"
|
||||
shift 2
|
||||
;;
|
||||
--version)
|
||||
packageVersionToDeploy="${2}"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option [${1}]"
|
||||
exit "${LINENO}"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
function cleanDirectory {
|
||||
local directory="${1}"
|
||||
|
||||
if [ -d "${directory}" ]; then
|
||||
echo "Deleting [${directory}]..."
|
||||
|
||||
rm -rf "${directory}"
|
||||
fi
|
||||
}
|
||||
|
||||
function removePackageJsonMember {
|
||||
local packageJsonPath="dist/package.json"
|
||||
local memberToRemove="${1}"
|
||||
|
||||
if [ -f "${packageJsonPath}" ]; then
|
||||
echo "Removing [${memberToRemove}] from the dist/package.json"
|
||||
|
||||
jq "del(.${memberToRemove})" "${packageJsonPath}" >tmp.$$.json && mv tmp.$$.json "$packageJsonPath"
|
||||
else
|
||||
echo "Error: [${packageJsonPath}] not found."
|
||||
fi
|
||||
}
|
||||
|
||||
function updatePackageJsonVersion {
|
||||
local versionToUpdate="${1}"
|
||||
|
||||
if [ isBeta == "true" ]; then
|
||||
echo "Version to update [${versionToUpdate}] Contains beta"
|
||||
echo "Updating package.json version to [${versionToUpdate}]"
|
||||
|
||||
local packageJsonVersion=$(jq -r '.version' package.json)
|
||||
|
||||
sed -i "s/\"version\": \"${packageJsonVersion}\"/\"version\": \"${versionToUpdate}\"/" dist/package.json
|
||||
fi
|
||||
}
|
||||
|
||||
echo "Deploying [${packageVersionToDeploy}]"
|
||||
echo "isBeta [${isBeta}]"
|
||||
|
||||
cleanDirectory "dist"
|
||||
|
||||
bun run ci-build
|
||||
|
||||
removePackageJsonMember "devDependencies"
|
||||
removePackageJsonMember "scripts"
|
||||
|
||||
echo "publishing to ${registryUrl}"
|
||||
if [ "${isBeta}" == "true" ]; then
|
||||
updatePackageJsonVersion "${packageVersionToDeploy}"
|
||||
npm publish --registry "${registryUrl}" --tag beta
|
||||
else
|
||||
npm publish --registry "${registryUrl}"
|
||||
fi
|
||||
|
||||
16
scripts/prepare-package-json.sh
Executable file
16
scripts/prepare-package-json.sh
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
distDirectory=${DIST_DIRECTORY:-"dist"}
|
||||
packageJsonPath=${PACKAGE_JSON_PATH:-"package.json"}
|
||||
|
||||
if [ ! -d "${distDirectory}" ]; then
|
||||
echo "Unable to prepare package.json, [${distDirectory}] not found"
|
||||
exit $LINENO
|
||||
fi
|
||||
|
||||
echo "Preparing [package.json] to [${distDirectory}]"
|
||||
jq '{name, version, author, type, types, exports, files, publishConfig}' "${packageJsonPath}" > "${distDirectory}/package.json"
|
||||
3
src/assertions/assertUnreachable.ts
Normal file
3
src/assertions/assertUnreachable.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function assertUnreachable(never: never): never {
|
||||
throw new Error(`Should not have reached [${never}]`);
|
||||
}
|
||||
3
src/assertions/index.ts
Normal file
3
src/assertions/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import assertUnreachable from './assertUnreachable';
|
||||
|
||||
export {assertUnreachable};
|
||||
23
src/disposables/Disposable.ts
Normal file
23
src/disposables/Disposable.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import type IDisposable from './IDisposable';
|
||||
|
||||
export default class Disposable implements IDisposable {
|
||||
private _isDisposed = false;
|
||||
private _disposable: () => void;
|
||||
|
||||
constructor(disposable: () => void) {
|
||||
this._disposable = disposable;
|
||||
}
|
||||
|
||||
get isDisposed(): boolean {
|
||||
return this._isDisposed;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this._isDisposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._disposable();
|
||||
this._isDisposed = true;
|
||||
}
|
||||
}
|
||||
17
src/disposables/DisposableList.ts
Normal file
17
src/disposables/DisposableList.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type IDisposable from './IDisposable';
|
||||
|
||||
export default class DisposableList implements IDisposable {
|
||||
private readonly _disposables: IDisposable[] = [];
|
||||
|
||||
public add(disposable: IDisposable): void {
|
||||
this._disposables.push(disposable);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
while (this._disposables.length) {
|
||||
const disposable = this._disposables.shift() as IDisposable;
|
||||
|
||||
disposable.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
3
src/disposables/IDisposable.ts
Normal file
3
src/disposables/IDisposable.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default interface IDisposable {
|
||||
dispose(): void;
|
||||
}
|
||||
6
src/disposables/index.ts
Normal file
6
src/disposables/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import type IDisposable from './IDisposable';
|
||||
import Disposable from './Disposable';
|
||||
import DisposableList from './DisposableList';
|
||||
|
||||
export type {IDisposable};
|
||||
export {Disposable, DisposableList};
|
||||
23
src/events/EventEmitter.ts
Normal file
23
src/events/EventEmitter.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import type IDisposable from '../disposables/IDisposable';
|
||||
import type IEvent from './IEvent';
|
||||
import {Disposable} from '../disposables';
|
||||
|
||||
export default class EventEmitter<T> implements IDisposable {
|
||||
private readonly _listeners: Array<(event: IEvent<T>) => void> = [];
|
||||
|
||||
public subscribe(listener: (event: IEvent<T>) => void): Disposable {
|
||||
const listenerIndex = this._listeners.push(listener);
|
||||
|
||||
return new Disposable(() => this._listeners.splice(listenerIndex - 1, 1));
|
||||
}
|
||||
|
||||
public emit<Event extends IEvent<T>>(event: Event): void {
|
||||
this._listeners.forEach(listener => listener(event));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
while (this._listeners.length) {
|
||||
this._listeners.shift();
|
||||
}
|
||||
}
|
||||
}
|
||||
39
src/events/EventPublisher.ts
Normal file
39
src/events/EventPublisher.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import type IEvent from './IEvent';
|
||||
import {Disposable, DisposableList} from '../disposables';
|
||||
import EventEmitter from './EventEmitter';
|
||||
|
||||
export default class EventPublisher {
|
||||
private readonly _eventEmitters: Record<string, EventEmitter<any>> = {};
|
||||
private readonly _disposables = new DisposableList();
|
||||
|
||||
constructor() {
|
||||
this._eventEmitters['no-subscribers'] = new EventEmitter();
|
||||
}
|
||||
|
||||
public addNoSubscriberHandler(handler: (event: IEvent<any>) => Promise<void> | void) {
|
||||
return this._eventEmitters['no-subscribers'].subscribe(handler);
|
||||
}
|
||||
|
||||
public subscribe<T>(event: string | number, handler: (event: IEvent<T>) => Promise<void> | void): Disposable {
|
||||
const emitter = this._eventEmitters[event] ?? this.createEmitter<T>(event);
|
||||
const subscription = emitter.subscribe(handler);
|
||||
|
||||
this._disposables.add(subscription);
|
||||
|
||||
return subscription;
|
||||
}
|
||||
|
||||
public publish<T>(eventName: string | number, event: IEvent<T>): void {
|
||||
(this._eventEmitters[eventName] || this._eventEmitters['no-subscribers']).emit<IEvent<T>>(event);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
Object.values(this._eventEmitters).forEach(emitter => emitter.dispose());
|
||||
}
|
||||
|
||||
private createEmitter<T>(event: string | number): EventEmitter<IEvent<T>> {
|
||||
const eventEmitter = new EventEmitter<IEvent<T>>();
|
||||
|
||||
return (this._eventEmitters[event] = eventEmitter);
|
||||
}
|
||||
}
|
||||
4
src/events/IEvent.ts
Normal file
4
src/events/IEvent.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export default interface IEvent<Payload> {
|
||||
type: string;
|
||||
payload?: Payload;
|
||||
}
|
||||
6
src/events/index.ts
Normal file
6
src/events/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import type IEvent from './IEvent';
|
||||
import EventEmitter from './EventEmitter';
|
||||
import EventPublisher from './EventPublisher';
|
||||
|
||||
export type {IEvent};
|
||||
export {EventEmitter, EventPublisher};
|
||||
3
src/functions/index.ts
Normal file
3
src/functions/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import {createRangeIterator} from './rangeIterator';
|
||||
|
||||
export {createRangeIterator};
|
||||
16
src/functions/rangeIterator.ts
Normal file
16
src/functions/rangeIterator.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export function createRangeIterator(start: number, end: number, step = 1) {
|
||||
return {
|
||||
[Symbol.iterator]() {
|
||||
return this;
|
||||
},
|
||||
next() {
|
||||
if (start < end) {
|
||||
start = start + step;
|
||||
|
||||
return {value: start, done: false};
|
||||
}
|
||||
|
||||
return {value: end, done: true};
|
||||
}
|
||||
};
|
||||
}
|
||||
12
src/index.ts
Normal file
12
src/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type IDisposable from './disposables/IDisposable';
|
||||
import type IEvent from './events/IEvent';
|
||||
|
||||
export type {IDisposable, IEvent};
|
||||
|
||||
export {assertUnreachable} from './assertions';
|
||||
export {Disposable, DisposableList} from './disposables';
|
||||
export {EventEmitter, EventPublisher} from './events';
|
||||
export {Averager, Random} from './maths';
|
||||
export {Subject, ReadOnlySubject} from './observables';
|
||||
export {Strings} from './strings';
|
||||
export {createRangeIterator} from './functions';
|
||||
33
src/maths/Averager.ts
Normal file
33
src/maths/Averager.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import {Subject, ReadOnlySubject} from '../observables';
|
||||
|
||||
export default class Averager {
|
||||
private readonly _sampleSize: number;
|
||||
private readonly _samples: number[] = [];
|
||||
private readonly _average = new Subject<number>(0);
|
||||
private readonly _readOnlyAverage = new ReadOnlySubject<number>(this._average);
|
||||
private _onPush: (sample: number) => void = this.onPushOnly.bind(this);
|
||||
|
||||
constructor(sampleSize: number) {
|
||||
this._sampleSize = sampleSize;
|
||||
}
|
||||
|
||||
get average(): ReadOnlySubject<number> {
|
||||
return this._readOnlyAverage;
|
||||
}
|
||||
|
||||
public push(value: number): void {
|
||||
this._onPush(value);
|
||||
}
|
||||
|
||||
private onPushOnly(sample: number) {
|
||||
if (this._samples.push(sample) === this._sampleSize) {
|
||||
this._onPush = this.pushAndCalculateAverage.bind(this);
|
||||
}
|
||||
}
|
||||
|
||||
private pushAndCalculateAverage(sample: number) {
|
||||
this._samples.push(sample);
|
||||
this._samples.shift();
|
||||
this._average.value = this._samples.reduce((sum, sampleValue) => sum + sampleValue, 0) / this._sampleSize;
|
||||
}
|
||||
}
|
||||
23
src/maths/Random.ts
Normal file
23
src/maths/Random.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export default class Random {
|
||||
private static readonly lowercaseLetters = 'abcdefghijklmnopqrstuvwxyz';
|
||||
private static readonly uppercaseLatters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
private static readonly letters = this.lowercaseLetters + this.uppercaseLatters;
|
||||
|
||||
public static number(): number {
|
||||
return Math.random();
|
||||
}
|
||||
|
||||
public static numberInRange(min: number, max: number): number {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
public static string(length: number): string {
|
||||
let result = '';
|
||||
|
||||
for (let idx = 0; idx < length; idx++) {
|
||||
result += this.letters[this.numberInRange(0, this.letters.length - 1)];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
4
src/maths/index.ts
Normal file
4
src/maths/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import Averager from './Averager';
|
||||
import Random from './Random';
|
||||
|
||||
export {Averager, Random};
|
||||
18
src/observables/ReadOnlySubject.ts
Normal file
18
src/observables/ReadOnlySubject.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import Disposable from '../disposables/Disposable';
|
||||
import Subject from './Subject';
|
||||
|
||||
export default class ReadOnlySubject<T> {
|
||||
private readonly _subject: Subject<T>;
|
||||
|
||||
constructor(subject: Subject<T>) {
|
||||
this._subject = subject;
|
||||
}
|
||||
|
||||
get value(): T {
|
||||
return this._subject.value;
|
||||
}
|
||||
|
||||
public subscribe(listener: (value: T) => void): Disposable {
|
||||
return this._subject.subscribe(listener);
|
||||
}
|
||||
}
|
||||
39
src/observables/Subject.ts
Normal file
39
src/observables/Subject.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import Disposable from '../disposables/Disposable';
|
||||
|
||||
export default class Subject<T> {
|
||||
private readonly _listeners: Array<(value: T) => void> = [];
|
||||
private _value: T;
|
||||
|
||||
constructor(value: T) {
|
||||
this._value = value;
|
||||
}
|
||||
|
||||
set value(value: T) {
|
||||
if (this._value === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._value = value;
|
||||
this.notifyListeners(value);
|
||||
}
|
||||
|
||||
get value(): T {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
public subscribe(listener: (value: T) => void): Disposable {
|
||||
const listenerIndex = this._listeners.push(listener);
|
||||
|
||||
listener(this.value);
|
||||
|
||||
return new Disposable(() => {
|
||||
this._listeners.splice(listenerIndex, 1);
|
||||
});
|
||||
}
|
||||
|
||||
private notifyListeners(value: T): void {
|
||||
for (const listener of this._listeners) {
|
||||
listener(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
4
src/observables/index.ts
Normal file
4
src/observables/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import Subject from './Subject';
|
||||
import ReadOnlySubject from './ReadOnlySubject';
|
||||
|
||||
export {Subject, ReadOnlySubject};
|
||||
15
src/strings/index.ts
Normal file
15
src/strings/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export class Strings {
|
||||
static random(length = 8) {
|
||||
if (length > 11) {
|
||||
throw new Error(`[Strings] max length [11]`);
|
||||
}
|
||||
|
||||
return Math.random()
|
||||
.toString(36)
|
||||
.substring(2, length + 2);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
throw new Error('Strings is a static class that may not be instantiated');
|
||||
}
|
||||
}
|
||||
10
test/assertions/assertUnreachable.test.ts
Normal file
10
test/assertions/assertUnreachable.test.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import {describe, it, expect} from 'bun:test';
|
||||
import {assertUnreachable} from '../../src/assertions';
|
||||
|
||||
describe(`When asserting unreachable code`, () => {
|
||||
it(`throws an error`, () => {
|
||||
const value = 'NEVER_VALUE';
|
||||
|
||||
expect(() => assertUnreachable(value)).toThrowError();
|
||||
});
|
||||
});
|
||||
41
test/disposables/Disposable.test.ts
Normal file
41
test/disposables/Disposable.test.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import type {Mock} from 'bun:test';
|
||||
import {mock, describe, it, beforeEach, expect} from 'bun:test';
|
||||
import {Disposable} from '../../src/disposables';
|
||||
|
||||
describe(`When using a Disposable`, () => {
|
||||
describe(`Given a Disposable`, () => {
|
||||
let mockFunction: Mock<any>;
|
||||
let disposable: Disposable;
|
||||
|
||||
beforeEach(() => {
|
||||
mockFunction = mock(() => {});
|
||||
disposable = new Disposable(mockFunction);
|
||||
});
|
||||
|
||||
describe(`Given the disposable is disposed`, () => {
|
||||
beforeEach(() => {
|
||||
disposable.dispose();
|
||||
});
|
||||
|
||||
it(`executes the disposble`, () => {
|
||||
expect(mockFunction).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it(`marks the disposbale disposed`, () => {
|
||||
expect(disposable.isDisposed).toBeTrue();
|
||||
});
|
||||
|
||||
describe(`Given the disposable is repeatedly disposed`, () => {
|
||||
beforeEach(() => {
|
||||
for (let count = 0; count < 10; count++) {
|
||||
disposable.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
it(`disposes the disposable only once`, () => {
|
||||
expect(mockFunction).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
36
test/disposables/DisposableList.test.ts
Normal file
36
test/disposables/DisposableList.test.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import type {Mock} from 'bun:test';
|
||||
import {mock, describe, it, beforeAll, beforeEach, expect} from 'bun:test';
|
||||
import {Disposable, DisposableList} from '../../src/disposables';
|
||||
|
||||
describe(`When using a DisposableList`, () => {
|
||||
let disposableList: DisposableList;
|
||||
|
||||
beforeEach(() => {
|
||||
disposableList = new DisposableList();
|
||||
});
|
||||
|
||||
describe(`Given a disposbale list with multiple disposables`, () => {
|
||||
let mocks: Array<Mock<any>>;
|
||||
let mockDisposables: Disposable[];
|
||||
|
||||
beforeEach(() => {
|
||||
mocks = [mock(() => {}), mock(() => {}), mock(() => {}), mock(() => {})];
|
||||
mockDisposables = mocks.map(mock => new Disposable(mock));
|
||||
mockDisposables.forEach(mockDisposable => disposableList.add(mockDisposable));
|
||||
});
|
||||
|
||||
describe(`Given the disposable list is disposed`, () => {
|
||||
beforeEach(() => {
|
||||
disposableList.dispose();
|
||||
});
|
||||
|
||||
it(`disposes all of the disposables in the list`, () => {
|
||||
mocks.forEach(mock => expect(mock).toHaveBeenCalled());
|
||||
});
|
||||
|
||||
it(`marks the disposable as disposed`, () => {
|
||||
mockDisposables.forEach(disposable => expect(disposable.isDisposed).toBeTrue());
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
19
test/events/EventEmitter.test.ts
Normal file
19
test/events/EventEmitter.test.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import {afterAll, beforeEach, describe, expect, it, mock} from 'bun:test';
|
||||
import {EventEmitter} from '../../src/events';
|
||||
|
||||
describe(`When emitting an event`, () => {
|
||||
const eventEmitter: EventEmitter<string> = new EventEmitter<string>();
|
||||
|
||||
describe(`Given a callback is subscribed`, () => {
|
||||
const mockListener = mock(() => undefined);
|
||||
const subscription = eventEmitter.subscribe(mockListener);
|
||||
|
||||
describe(`Given an event is emitted`, () => {
|
||||
beforeEach(() => eventEmitter.emit('mock event'));
|
||||
|
||||
it(`notifies the callback with the event`, () => expect(mockListener).toHaveBeenCalled());
|
||||
});
|
||||
|
||||
afterAll(() => subscription.dispose());
|
||||
});
|
||||
});
|
||||
3
test/events/EventPublisher.test.ts
Normal file
3
test/events/EventPublisher.test.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import {describe} from 'bun:test';
|
||||
|
||||
describe.todo(`When publishing events`);
|
||||
20
test/functions/range.test.ts
Normal file
20
test/functions/range.test.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import {describe, it, expect, mock} from 'bun:test';
|
||||
import {createRangeIterator} from '../../src/functions';
|
||||
|
||||
describe('When generating a range iterator', () => {
|
||||
describe('Given start and stop values', () => {
|
||||
const start = 1;
|
||||
const stop = 10;
|
||||
const mockFn = mock(() => undefined);
|
||||
|
||||
it('generates an iterator of the correct length', () => {
|
||||
const rangeIterator = createRangeIterator(1, 10);
|
||||
|
||||
for (const idx of rangeIterator) {
|
||||
mockFn();
|
||||
}
|
||||
|
||||
expect(mockFn).toHaveBeenCalledTimes(9);
|
||||
});
|
||||
});
|
||||
});
|
||||
60
test/observables/ReadOnlySubject.test.ts
Normal file
60
test/observables/ReadOnlySubject.test.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import {jest, describe, it, expect} from 'bun:test';
|
||||
import {Subject, ReadOnlySubject} from '../../src/observables';
|
||||
|
||||
describe(`A ReadOnlySubject`, () => {
|
||||
it(`exposes the Subject value`, () => {
|
||||
const subject = new Subject<string>(`init-value`);
|
||||
const readOnlySubject = new ReadOnlySubject(subject);
|
||||
|
||||
expect(readOnlySubject.value).toBe(`init-value`);
|
||||
});
|
||||
|
||||
it(`updates the ReadOnlySubject value when set`, () => {
|
||||
const subject = new Subject<string>('init-value');
|
||||
const readOnlySubject = new ReadOnlySubject(subject);
|
||||
|
||||
subject.value = 'some thing';
|
||||
|
||||
expect(readOnlySubject.value).toBe('some thing');
|
||||
});
|
||||
|
||||
it(`notifies subcribers when subscribing`, () => {
|
||||
const subject = new Subject<string>('init-value');
|
||||
const readOnlySubject = new ReadOnlySubject(subject);
|
||||
const listener = jest.fn() as (value: string) => void;
|
||||
|
||||
readOnlySubject.subscribe(listener);
|
||||
|
||||
expect(listener).toHaveBeenCalledWith('init-value');
|
||||
});
|
||||
|
||||
it(`notifies subcribers when value changes`, () => {
|
||||
const subject = new Subject<string>('init-value');
|
||||
const readOnlySubject = new ReadOnlySubject(subject);
|
||||
const listener = jest.fn() as (value: string) => void;
|
||||
|
||||
readOnlySubject.subscribe(listener);
|
||||
|
||||
subject.value = 'some thing';
|
||||
|
||||
expect(listener).toHaveBeenCalledWith('some thing');
|
||||
});
|
||||
|
||||
it(`notifies all subcribers when value changes`, () => {
|
||||
const subject = new Subject<string>('init-value');
|
||||
const readOnlySubject = new ReadOnlySubject(subject);
|
||||
const listener1 = jest.fn() as (value: string) => void;
|
||||
const listener2 = jest.fn() as (value: string) => void;
|
||||
const listener3 = jest.fn() as (value: string) => void;
|
||||
|
||||
readOnlySubject.subscribe(listener1);
|
||||
readOnlySubject.subscribe(listener2);
|
||||
readOnlySubject.subscribe(listener3);
|
||||
|
||||
subject.value = 'some thing';
|
||||
|
||||
expect(listener1).toHaveBeenCalledWith('some thing');
|
||||
expect(listener2).toHaveBeenCalledWith('some thing');
|
||||
expect(listener3).toHaveBeenCalledWith('some thing');
|
||||
});
|
||||
});
|
||||
55
test/observables/Subject.test.ts
Normal file
55
test/observables/Subject.test.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import {jest, describe, it, expect} from 'bun:test';
|
||||
import {Subject} from '../../src/observables';
|
||||
|
||||
describe('A Subject', () => {
|
||||
it('exposes the Subject value', () => {
|
||||
const subject = new Subject<string>('init-value');
|
||||
|
||||
expect(subject.value).toBe('init-value');
|
||||
});
|
||||
|
||||
it('updates the Subject value when set', () => {
|
||||
const subject = new Subject<string>('init-value');
|
||||
|
||||
subject.value = 'some thing';
|
||||
|
||||
expect(subject.value).toBe('some thing');
|
||||
});
|
||||
|
||||
it('notifies subcribers when subscribing', () => {
|
||||
const subject = new Subject<string>('init-value');
|
||||
const listener = jest.fn() as (value: string) => void;
|
||||
|
||||
subject.subscribe(listener);
|
||||
|
||||
expect(listener).toHaveBeenCalledWith('init-value');
|
||||
});
|
||||
|
||||
it('notifies subcribers when value changes', () => {
|
||||
const subject = new Subject<string>('init-value');
|
||||
const listener = jest.fn() as (value: string) => void;
|
||||
|
||||
subject.subscribe(listener);
|
||||
|
||||
subject.value = 'some thing';
|
||||
|
||||
expect(listener).toHaveBeenCalledWith('some thing');
|
||||
});
|
||||
|
||||
it('notifies all subcribers when value changes', () => {
|
||||
const subject = new Subject<string>('init-value');
|
||||
const listener1 = jest.fn() as (value: string) => void;
|
||||
const listener2 = jest.fn() as (value: string) => void;
|
||||
const listener3 = jest.fn() as (value: string) => void;
|
||||
|
||||
subject.subscribe(listener1);
|
||||
subject.subscribe(listener2);
|
||||
subject.subscribe(listener3);
|
||||
|
||||
subject.value = 'some thing';
|
||||
|
||||
expect(listener1).toHaveBeenCalledWith('some thing');
|
||||
expect(listener2).toHaveBeenCalledWith('some thing');
|
||||
expect(listener3).toHaveBeenCalledWith('some thing');
|
||||
});
|
||||
});
|
||||
39
test/strings/WhenGeneratingRandomStrings.test.ts
Normal file
39
test/strings/WhenGeneratingRandomStrings.test.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import {describe, beforeAll, it, expect} from 'bun:test';
|
||||
import {Strings} from '../../src/strings';
|
||||
|
||||
const randomStringCount = 11;
|
||||
|
||||
describe('When generating random strings', () => {
|
||||
it('generates a random string', () => {
|
||||
const randomString = Strings.random();
|
||||
|
||||
expect(typeof randomString).toBe('string');
|
||||
expect(randomString.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
describe('Given a length argument', () => {
|
||||
const length = 4;
|
||||
|
||||
it('generates a random string of the given length', () => {
|
||||
const randomString = Strings.random(length);
|
||||
|
||||
expect(typeof randomString).toBe('string');
|
||||
expect(randomString.length).toBeGreaterThan(0);
|
||||
expect(randomString.length).toBe(length);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`Given [${randomStringCount}] are generated`, () => {
|
||||
let randomStrings;
|
||||
|
||||
beforeAll(() => {
|
||||
randomStrings = Array.from({length: randomStringCount}, () => Strings.random());
|
||||
});
|
||||
|
||||
it('generates random strings such that no strings are identical', () => {
|
||||
const uniqueRandomStrings = new Set(randomStrings);
|
||||
|
||||
expect(uniqueRandomStrings.size).toBe(randomStringCount);
|
||||
});
|
||||
});
|
||||
});
|
||||
30
tsconfig.d.json
Normal file
30
tsconfig.d.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Enable latest features
|
||||
"lib": ["esnext", "dom"],
|
||||
"target": "es6",
|
||||
"module": "esnext",
|
||||
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"esModuleInterop": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"allowArbitraryExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"declarationDir": "dist/types",
|
||||
|
||||
// Best practices
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["./dist", "./test"]
|
||||
}
|
||||
28
tsconfig.json
Normal file
28
tsconfig.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Enable latest features
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"allowJs": true,
|
||||
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
// Best practices
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["./dist"]
|
||||
}
|
||||
Reference in New Issue
Block a user