nanosplash 4.0.2 → 4.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.env ADDED
@@ -0,0 +1 @@
1
+ CODECOV_TOKEN=4e686913-a962-407a-a847-7837e4f61be4
@@ -1,9 +1,6 @@
1
- name: Build and Test
1
+ name: CI Production - Build, Test, Publish
2
2
 
3
3
  on:
4
- push:
5
- branches:
6
- - production
7
4
  pull_request:
8
5
  branches:
9
6
  - production
@@ -13,19 +10,20 @@ jobs:
13
10
  runs-on: ubuntu-latest
14
11
 
15
12
  steps:
16
- - name: Checkout
17
- uses: actions/checkout@v3
13
+ - name: Install Node.js
14
+ uses: actions/setup-node@v3
15
+ with:
16
+ node-version: 20
17
+ registry-url: 'https://registry.npmjs.org'
18
18
 
19
19
  - name: Install PNPM
20
20
  uses: pnpm/action-setup@v3
21
21
  with:
22
22
  version: 8
23
23
 
24
- - name: Setup Node.js
25
- uses: actions/setup-node@v3
26
- with:
27
- node-version: 20
28
- registry-url: 'https://registry.npmjs.org'
24
+ - name: Checkout
25
+ uses: actions/checkout@v3
26
+
29
27
  - name: Set up Git and update production branch
30
28
  run: |
31
29
  git config --global user.name "GitHub Actions"
@@ -34,27 +32,26 @@ jobs:
34
32
  git checkout production
35
33
  git pull --no-rebase
36
34
 
37
- - name: Setup Pages
35
+ - name: Setup GitHub Pages
38
36
  uses: actions/configure-pages@v3
39
37
 
40
38
  - name: Install dependencies
41
39
  run: pnpm install --no-frozen-lockfile
42
40
 
43
- - name: Build (Vite)
41
+ - name: Build
44
42
  run: pnpm build
45
43
 
46
- - name: Test (Vitest)
44
+ - name: Test
47
45
  run: pnpm test
48
46
 
49
- - name: Generate Code Coverage (Vitest)
47
+ - name: Generate Code Coverage
50
48
  run: pnpm coverage
51
49
 
52
- - name: Commit and push changes
53
- run: |
54
- git add .
55
- git add -f coverage/**/* dist/**/*
56
- git commit --allow-empty -m "Build, test, and coverage report"
57
- git push
50
+ - name: Upload coverage reports to Codecov
51
+ uses: codecov/codecov-action@v4.0.1
52
+ with:
53
+ token: ${{ secrets.CODECOV_TOKEN }}
54
+ slug: isakhauge/nanosplash
58
55
 
59
56
  - name: Publish to NPM
60
57
  run: npm publish
@@ -0,0 +1,48 @@
1
+ name: CI Test - Build, Test
2
+
3
+ on:
4
+ pull_request:
5
+ branches:
6
+ - test
7
+
8
+ jobs:
9
+ build-and-test:
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+ - name: Install Node.js
14
+ uses: actions/setup-node@v3
15
+ with:
16
+ node-version: 20
17
+ registry-url: 'https://registry.npmjs.org'
18
+
19
+ - name: Install PNPM
20
+ uses: pnpm/action-setup@v3
21
+ with:
22
+ version: 8
23
+
24
+ - name: Checkout
25
+ uses: actions/checkout@v3
26
+
27
+ - name: Set up Git and update production branch
28
+ run: |
29
+ git config --global user.name "GitHub Actions"
30
+ git config --global user.email "actions@github.com"
31
+ git fetch
32
+ git checkout test
33
+ git pull --no-rebase
34
+
35
+ - name: Setup GitHub Pages
36
+ uses: actions/configure-pages@v3
37
+
38
+ - name: Install dependencies
39
+ run: pnpm install --no-frozen-lockfile
40
+
41
+ - name: Build
42
+ run: pnpm build
43
+
44
+ - name: Test
45
+ run: pnpm test
46
+
47
+ - name: Generate Code Coverage
48
+ run: pnpm coverage
@@ -41,16 +41,17 @@ jobs:
41
41
  uses: actions/setup-node@v3
42
42
  with:
43
43
  node-version: 20
44
- - name: Setup Pages
44
+ - name: Setup GitHub Pages
45
45
  uses: actions/configure-pages@v3
46
46
  - name: Install dependencies
47
47
  run: pnpm install --no-frozen-lockfile
48
48
  - name: Build with VitePress
49
49
  run: |
50
+ pnpm build:es
50
51
  pnpm docs:build
51
52
  touch docs/.vitepress/dist/.nojekyll
52
53
  - name: Upload artifact
53
- uses: actions/upload-pages-artifact@v2
54
+ uses: actions/upload-pages-artifact@v3
54
55
  with:
55
56
  path: docs/.vitepress/dist
56
57
 
@@ -65,4 +66,4 @@ jobs:
65
66
  steps:
66
67
  - name: Deploy to GitHub Pages
67
68
  id: deployment
68
- uses: actions/deploy-pages@v2
69
+ uses: actions/deploy-pages@v4
package/README.md CHANGED
@@ -4,7 +4,8 @@
4
4
 
5
5
  <br>
6
6
 
7
- [![Build](https://github.com/isakhauge/nanosplash/actions/workflows/ci.yml/badge.svg?branch=production)](https://github.com/isakhauge/nanosplash/actions/workflows/ci.yml)
7
+ [![Build](https://github.com/isakhauge/nanosplash/actions/workflows/ci.prod.yml/badge.svg?branch=production)](https://github.com/isakhauge/nanosplash/actions/workflows/ci.prod.yml)
8
+ [![codecov](https://codecov.io/gh/isakhauge/nanosplash/graph/badge.svg?token=KDUC5H82UE)](https://codecov.io/gh/isakhauge/nanosplash)
8
9
 
9
10
  ## 💖 Documentation
10
11
 
@@ -3,7 +3,7 @@ import CardGrid from './CardGrid.vue'
3
3
  import Card from './Card.vue'
4
4
  import { useNs } from '../../../../dist/es/ns'
5
5
  if (window) {
6
- window.ns = useNs(window)
6
+ window.ns = useNs()
7
7
  }
8
8
  </script>
9
9
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nanosplash",
3
- "version": "4.0.2",
3
+ "version": "4.0.3",
4
4
  "author": {
5
5
  "name": "Isak K. Hauge",
6
6
  "email": "isakhauge@icloud.com",
@@ -13,11 +13,13 @@
13
13
  "main": "./dist/es/ns.js",
14
14
  "module": "./dist/es/ns.js",
15
15
  "devDependencies": {
16
+ "@codecov/vite-plugin": "0.0.1-beta.5",
16
17
  "@types/jsdom": "^21.1.6",
17
18
  "@types/node": "^20.11.30",
18
19
  "@vitest/coverage-v8": "^1.4.0",
19
20
  "@vitest/ui": "^1.4.0",
20
21
  "cross-env": "^7.0.3",
22
+ "dotenv": "^16.4.5",
21
23
  "eslint": "^8.57.0",
22
24
  "eslint-config-prettier": "^9.1.0",
23
25
  "eslint-plugin-prettier": "^5.1.3",
@@ -2,21 +2,19 @@
2
2
  import style from '../style/ns.sass?inline'
3
3
  import { NanosplashInterface } from './types/NanosplashInterface'
4
4
  import { ElementReference, NSElement } from './types/Types'
5
- import type { DOMWindow } from 'jsdom'
6
5
  import { version } from '../../package.json'
7
6
 
8
7
  /**
9
8
  * # Use Nanosplash
10
9
  * @returns Nanosplash API.
11
10
  */
12
- export const useNs = (
13
- win?: (Window | (Window & typeof globalThis)) | DOMWindow
14
- ): NanosplashInterface => {
15
- const doc = (win ?? window).document
11
+ export const useNs = (): NanosplashInterface => {
12
+ const doc = window.document
16
13
  const bod = doc.body
17
14
 
15
+ const toArray = <T>(iter: Iterable<T> | ArrayLike<T>) => Array.from(iter)
18
16
  const all = (node: ParentNode, ref: string) =>
19
- Array.from(node.querySelectorAll(ref))
17
+ toArray(node.querySelectorAll(ref))
20
18
  const first = (node: ParentNode, ref: string) => all(node, ref)[0] ?? null
21
19
 
22
20
  /**
@@ -121,13 +119,14 @@ export const useNs = (
121
119
  * @returns The ID if the NS element — which is a UTC integer.
122
120
  */
123
121
  const show = (text?: string, inside?: string | Element): number | null => {
124
- const parent: Element | null = (
125
- inside ? parseRef(inside) : bod
126
- ) as Element | null
122
+ const parent: Element = (inside ? parseRef(inside) ?? bod : bod) as Element
127
123
  let ns: NSElement
128
- const recycledNs = first(parent ?? bod, '& > .ns')
129
- if (recycledNs) {
130
- ns = recycledNs as NSElement
124
+ const recycled: Element | undefined = toArray(parent.children)
125
+ .filter(node => node.classList.contains('ns'))
126
+ .at(0)
127
+ const isNs = recycled?.classList.contains('ns')
128
+ if (isNs) {
129
+ ns = recycled as NSElement
131
130
  } else {
132
131
  ns = makeNs()
133
132
  setNsParent(ns, parent as Element)
package/src/ts/main.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  // @ts-strict
2
2
  import { useNs } from './Nanosplash'
3
3
  window.addEventListener('load', function () {
4
- this.ns = useNs(this)
4
+ this.ns = useNs()
5
5
  })
@@ -4,11 +4,14 @@ export interface NanosplashInterface {
4
4
  /**
5
5
  * # Show
6
6
  * Display Nanosplash inside the window.
7
- * @param {string | undefined} text Optional text
7
+ * @param {string | null | undefined} text Optional text. Pass falsy to display spinner only.
8
8
  * @param {string | Element | undefined} inside Optional element reference
9
9
  * @returns {number | null} The internal ID of the created Nanosplash.
10
10
  */
11
- show(text?: string, inside?: DOMSelector | HTMLElement): UTCInteger | null
11
+ show(
12
+ text?: string | null | undefined,
13
+ inside?: DOMSelector | HTMLElement
14
+ ): UTCInteger | null
12
15
  /**
13
16
  * # Hide
14
17
  * Remove Nanosplash in multiple ways depending on the passed argument.
@@ -1,25 +1,207 @@
1
- import { describe, it, expect, beforeEach } from 'vitest'
2
- import { JSDOM } from 'jsdom'
1
+ import { describe, it, expect } from 'vitest'
3
2
  import { useNs } from '../src/ts/Nanosplash'
3
+ import { NSElement } from '../src/ts/types/Types'
4
4
 
5
- const dom = new JSDOM()
6
- var window = dom.window
7
- const ns = (window.ns = useNs(window))
5
+ const ns = (window.ns = useNs())
6
+ const get = (selector: string) => document.querySelector(selector)
7
+ const getAll = (selector: string) =>
8
+ Array.from(document.querySelectorAll(selector))
9
+ const allNs = () => getAll('.ns') as NSElement[]
10
+ const nsCount = () => allNs().length
8
11
 
9
- describe('Nanosplash API', () => {
10
- it('should be defined', () => {
12
+ const div = (id: string) => {
13
+ const node = document.createElement('div')
14
+ node.id = id
15
+ return node
16
+ }
17
+
18
+ describe('Global access', () => {
19
+ it('should have "ns"', () => {
11
20
  expect(window.ns).toBeDefined()
21
+ expect(window.ns).toBe(ns)
12
22
  })
13
23
 
14
- it('should have the "show" method', () => {
24
+ it('should have the "ns.show" function', () => {
15
25
  expect(window.ns.show).toBeDefined()
26
+ expect(window.ns.show).toBeTypeOf('function')
16
27
  })
17
28
 
18
- it('should have the "hide" method', () => {
29
+ it('should have the "ns.hide" function', () => {
19
30
  expect(window.ns.hide).toBeDefined()
31
+ expect(window.ns.hide).toBeTypeOf('function')
20
32
  })
33
+ })
21
34
 
22
- it('should have the global instance', () => {
23
- expect(window.ns).toBe(ns)
35
+ describe('useNs hook', () => {
36
+ it('should return the Nanosplash API', () => {
37
+ const instance = useNs()
38
+ expect(instance.show).toBeTypeOf('function')
39
+ expect(instance.hide).toBeTypeOf('function')
40
+ })
41
+ })
42
+
43
+ describe('Nanosplash API', () => {
44
+ describe('show / hide cleanup', () => {
45
+ describe('show', () => {
46
+ it('should make the body host', () => {
47
+ const id = ns.show()
48
+ const bod = document.body
49
+ const bodyIsHost = bod.classList.contains('nsh')
50
+ expect(bodyIsHost).toBe(true)
51
+ ns.hide(id as number)
52
+ expect(nsCount()).toBe(0)
53
+ })
54
+
55
+ it('should create a Nanosplash element', () => {
56
+ const id = ns.show()
57
+ const nsElement = get('body > .ns') as NSElement
58
+ expect(nsElement).toBeInstanceOf(HTMLDivElement)
59
+ ns.hide(id as number)
60
+ expect(nsCount()).toBe(0)
61
+ })
62
+
63
+ it('should have the correct internal ID', () => {
64
+ const id = ns.show()
65
+ const nsElement = get('body > .ns') as NSElement
66
+ expect(id).toBe(nsElement.nsId)
67
+ ns.hide(id as number)
68
+ expect(nsCount()).toBe(0)
69
+ })
70
+ })
71
+
72
+ describe('hide', () => {
73
+ it('should be able to hide the element', () => {
74
+ const id = ns.show()
75
+ expect(nsCount()).toBeGreaterThan(0)
76
+ ns.hide(id as number)
77
+ expect(nsCount()).toBe(0)
78
+ })
79
+
80
+ it('should undo body as host', () => {
81
+ const id = ns.show()
82
+ ns.hide(id as number)
83
+ const bodyIsNoLongerHost = document.body.classList.contains('nsh')
84
+ expect(bodyIsNoLongerHost).toBe(false)
85
+ expect(nsCount()).toBe(0)
86
+ })
87
+
88
+ it('should remove the Nanosplash element from DOM', () => {
89
+ const id = ns.show()
90
+ const nsElement = get('body > .ns') as NSElement
91
+ ns.hide(id as number)
92
+ expect(nsElement?.isConnected).toBe(false)
93
+ expect(nsCount()).toBe(0)
94
+ })
95
+ })
96
+ })
97
+
98
+ describe('show()', () => {
99
+ it('should inject a Nanosplash in the body element', () => {
100
+ const id = ns.show()
101
+ const elements = allNs() as NSElement[]
102
+ const element = elements.find((v: NSElement) => v.nsId === id)
103
+ expect(element).toBeInstanceOf(HTMLDivElement)
104
+ ns.hide(id as number)
105
+ expect(nsCount()).toBe(0)
106
+ })
107
+
108
+ it('should only display a spinner', () => {
109
+ const id = ns.show()
110
+ const nst = get('.ns > .nst')
111
+ const nss = get('.ns > .nss')
112
+ expect(nst).toBeNull()
113
+ expect(nss).toBeInstanceOf(HTMLDivElement)
114
+ ns.hide(id as number)
115
+ expect(nsCount()).toBe(0)
116
+ })
117
+
118
+ it('should recycle elements', () => {
119
+ const idFirst = ns.show()
120
+ const idSecond = ns.show()
121
+ expect(nsCount()).toBe(1)
122
+ expect(idFirst).toBe(idSecond)
123
+ ns.hide()
124
+ ns.hide()
125
+ expect(nsCount()).toBe(0)
126
+ })
127
+ })
128
+
129
+ describe('show("foo", "#bar")', () => {
130
+ it('should display text inside #bar', () => {
131
+ const bar = div('bar')
132
+ document.body.append(bar)
133
+ const id = ns.show('foo', '#bar')
134
+ expect(nsCount()).toBe(1)
135
+ const selector = '#bar.nsh > .ns > .nst'
136
+ const nst = get(selector)
137
+ expect(nst).toBeInstanceOf(HTMLDivElement)
138
+ expect((nst as HTMLDivElement).innerHTML).toBe('foo')
139
+ ns.hide(id as number)
140
+ bar.remove()
141
+ expect(nsCount()).toBe(0)
142
+ })
143
+ })
144
+
145
+ describe('hide()', () => {
146
+ it('should hide in FIFO order', () => {
147
+ document.body.append(div('a'), div('b'), div('c'), div('d'))
148
+
149
+ ns.show(null, '#a')
150
+ ns.show(null, '#b')
151
+ ns.show(null, '#c')
152
+ ns.show(null, '#d')
153
+
154
+ const theseExist = (...ids: string[]) => ids.every(id => get(id))
155
+
156
+ expect(theseExist('#a', '#b', '#c', '#d')).toBe(true)
157
+ ns.hide()
158
+ expect(theseExist('#b', '#c', '#d')).toBe(true)
159
+ ns.hide()
160
+ expect(theseExist('#c', '#d')).toBe(true)
161
+ ns.hide()
162
+ expect(theseExist('#d')).toBe(true)
163
+ ns.hide()
164
+
165
+ expect(nsCount()).toBe(0)
166
+ getAll('#a, #b, #c, #d').forEach(node => node.remove())
167
+ expect(nsCount()).toBe(0)
168
+ })
169
+ })
170
+
171
+ describe('hide(id)', () => {
172
+ it('should delete a specific Nanosplash', () => {
173
+ const a = div('a')
174
+ const b = div('b')
175
+ document.body.append(a, b)
176
+ const idA = ns.show(null, '#a')
177
+ const idB = ns.show(null, '#b')
178
+ const bothExist = allNs().every(node => [idA, idB].includes(node.nsId))
179
+ expect(bothExist).toBe(true)
180
+ expect(nsCount()).toBe(2)
181
+ ns.hide(idB as number)
182
+ expect(nsCount()).toBe(1)
183
+ expect(allNs()[0].nsId).toBe(idA)
184
+ ns.hide(idA as number)
185
+ a.remove()
186
+ b.remove()
187
+ expect(nsCount()).toBe(0)
188
+ })
189
+ })
190
+
191
+ describe('hide(*)', () => {
192
+ it('should hide all Nanosplashes', () => {
193
+ document.body.append(div('a'), div('b'), div('c'))
194
+
195
+ ns.show(null, '#a')
196
+ ns.show(null, '#b')
197
+ ns.show(null, '#c')
198
+
199
+ expect(nsCount()).toBe(3)
200
+ ns.hide('*')
201
+ expect(nsCount()).toBe(0)
202
+
203
+ getAll('#a, #b, #c').forEach(node => node.remove())
204
+ expect(nsCount()).toBe(0)
205
+ })
24
206
  })
25
207
  })
package/vite.config.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  import { LibraryFormats, defineConfig } from 'vite'
2
+ import { codecovVitePlugin } from '@codecov/vite-plugin'
3
+ import { config } from 'dotenv'
4
+ config()
2
5
 
3
6
  const format = (process.env?.format ?? '') as LibraryFormats
4
7
  const entry = (process.env?.entry ?? '') as string
@@ -13,4 +16,18 @@ export default defineConfig({
13
16
  fileName: 'ns',
14
17
  },
15
18
  },
19
+ test: {
20
+ environment: 'jsdom',
21
+ globals: true,
22
+ coverage: {
23
+ reporter: ['text', 'json', 'html', 'lcov'],
24
+ },
25
+ },
26
+ plugins: [
27
+ codecovVitePlugin({
28
+ enableBundleAnalysis: process.env.CODECOV_TOKEN !== undefined,
29
+ bundleName: 'nanosplash',
30
+ uploadToken: process.env.CODECOV_TOKEN,
31
+ }),
32
+ ],
16
33
  })