@dbosoft/nextjs-uicore 1.5.1 → 1.6.1

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.
Files changed (30) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/eslint.config.mjs +3 -3
  3. package/package.json +11 -6
  4. package/src/head/index.tsx +119 -119
  5. package/src/subnav/helpers/useStuckRef.ts +50 -50
  6. package/src/subnav/index.tsx +134 -135
  7. package/src/subnav/partials/CtaLinks/github-stars-link/formatStarCount/index.test.js +25 -25
  8. package/src/subnav/partials/CtaLinks/github-stars-link/formatStarCount/index.ts +17 -17
  9. package/src/subnav/partials/CtaLinks/github-stars-link/index.tsx +96 -96
  10. package/src/subnav/partials/CtaLinks/github-stars-link/parseGithubUrl/index.test.js +25 -25
  11. package/src/subnav/partials/CtaLinks/github-stars-link/parseGithubUrl/index.ts +25 -25
  12. package/src/subnav/partials/CtaLinks/icons/github.svg +3 -3
  13. package/src/subnav/partials/CtaLinks/index.tsx +46 -46
  14. package/src/subnav/partials/MenuItemsDefault/index.tsx +52 -52
  15. package/src/subnav/partials/MenuItemsDefault/style.module.scss +71 -71
  16. package/src/subnav/partials/MenuItemsOverflow/index.tsx +51 -51
  17. package/src/subnav/partials/MenuItemsOverflow/style.module.scss +51 -51
  18. package/src/subnav/partials/TitleLink/index.tsx +25 -25
  19. package/src/subnav/partials/nav-item-text/index.tsx +29 -29
  20. package/src/subnav/partials/nav-item-text/style.module.scss +19 -19
  21. package/src/subnav/style.module.scss +13 -13
  22. package/src/tabs/Tabs.tsx +50 -50
  23. package/src/tabs/TabsClient.tsx +75 -75
  24. package/src/tabs/index.ts +3 -3
  25. package/src/tabs/server.ts +2 -2
  26. package/src/themeselector/index.tsx +139 -139
  27. package/src/translations.ts +25 -25
  28. package/tsconfig.json +7 -7
  29. package/.turbo/turbo-build.log +0 -4
  30. package/.turbo/turbo-check-types.log +0 -4
@@ -1,135 +1,134 @@
1
- import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react'
2
- import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline'
3
- import type { LinkType, Variant } from '@dbosoft/react-uicore/linkbutton'
4
- import MenuItemsOverflow from './partials/MenuItemsOverflow'
5
- import MenuItemsDefault from './partials/MenuItemsDefault'
6
- import CtaLinks from './partials/CtaLinks'
7
- import useStuckRef from './helpers/useStuckRef'
8
- import TitleLink from './partials/TitleLink'
9
- import style from './style.module.scss'
10
- import clsx from 'clsx'
11
- import { ThemeSelector } from '../themeselector'
12
-
13
- export type MenuItem = 'divider' | ILinkMenuItem;
14
-
15
- export interface IDropDownMenuItem {
16
- text: string,
17
- submenu: ILinkMenuItem[]
18
- }
19
-
20
- export interface ILinkMenuItem {
21
- text: string,
22
- url: string,
23
- type: 'inbound' | 'outbound' | 'anchor'
24
- active?: boolean
25
- }
26
-
27
- export interface ITitleLink {
28
- url: string,
29
- text?: string,
30
- }
31
-
32
-
33
- export interface ICtaItem {
34
- text: string,
35
- url: string,
36
- variant?: Variant
37
- linktype?: LinkType
38
- className?: string,
39
- }
40
-
41
- export interface SubNavLabels {
42
- openMainMenu: string
43
- }
44
-
45
- const defaultSubNavLabels: SubNavLabels = {
46
- openMainMenu: 'Open main menu',
47
- }
48
-
49
- interface ISubNavProps {
50
- titleLink?: ITitleLink,
51
- titleContent?: React.JSX.Element,
52
- ctaLinks: ICtaItem[],
53
- hideGithubStars: boolean
54
- menuItems: MenuItem[],
55
- menuItemsAlign: 'left' | 'right',
56
- className?: string,
57
- labels?: Partial<SubNavLabels>
58
- }
59
-
60
- function Subnav({
61
- className,
62
- titleLink,
63
- ctaLinks = [],
64
- hideGithubStars,
65
- menuItems,
66
- menuItemsAlign = `right`,
67
- titleContent,
68
- labels: labelsProp
69
- }: ISubNavProps) {
70
- const labels = { ...defaultSubNavLabels, ...labelsProp }
71
-
72
- const { isStuck, stuckRef } = useStuckRef([])
73
-
74
- return (
75
- <div className={clsx(style.root, `bg-white dark:bg-slate-900`, className, {
76
- [style.isSticky]: isStuck,
77
- })} ref={stuckRef}>
78
- <Disclosure as="nav" >
79
- {({ open }) => (
80
- <>
81
- <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
82
- <div className="flex justify-between h-16">
83
- <div className={clsx(`flex grow`, menuItemsAlign == `right` ? ` justify-between` : ``)}>
84
-
85
- <div className="flex-shrink-0 flex items-center ">
86
- {titleLink &&<TitleLink
87
- text={titleLink.text}
88
-
89
- url={titleLink.url} >{titleContent}</TitleLink>}
90
- {!titleLink && titleContent}
91
- </div>
92
- <div className="hidden sm:-my-px sm:ml-6 sm:flex">
93
- <MenuItemsDefault
94
- menuItems={menuItems}
95
- />
96
- <CtaLinks
97
- hideGithubStars={hideGithubStars}
98
- isInDropdown={false}
99
- links={ctaLinks}
100
- />
101
- </div>
102
- </div>
103
-
104
- <div className="-mr-2 flex items-center sm:hidden ">
105
- {/* Mobile menu button */}
106
- <DisclosureButton className="bg-white inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-secondary">
107
- <span className="sr-only">{labels.openMainMenu}</span>
108
- {open ? (
109
- <XMarkIcon aria-hidden="true" className="block h-6 w-6" />
110
- ) : (
111
- <Bars3Icon aria-hidden="true" className="block h-6 w-6" />
112
- )}
113
- </DisclosureButton>
114
- </div>
115
- </div>
116
-
117
- </div>
118
-
119
- <DisclosurePanel className="sm:hidden">
120
- <div className="pt-2 pb-3 space-y-1">
121
- <MenuItemsOverflow
122
- ctaLinks={ctaLinks}
123
- hideGithubStars={hideGithubStars}
124
- menuItems={menuItems}
125
- /> </div>
126
-
127
- </DisclosurePanel>
128
- </>
129
- )}
130
- </Disclosure>
131
- </div>
132
- )
133
- }
134
-
135
- export default Subnav
1
+ import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react'
2
+ import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline'
3
+ import type { LinkType, Variant } from '@dbosoft/react-uicore/linkbutton'
4
+ import MenuItemsOverflow from './partials/MenuItemsOverflow'
5
+ import MenuItemsDefault from './partials/MenuItemsDefault'
6
+ import CtaLinks from './partials/CtaLinks'
7
+ import useStuckRef from './helpers/useStuckRef'
8
+ import TitleLink from './partials/TitleLink'
9
+ import style from './style.module.scss'
10
+ import clsx from 'clsx'
11
+
12
+ export type MenuItem = 'divider' | ILinkMenuItem;
13
+
14
+ export interface IDropDownMenuItem {
15
+ text: string,
16
+ submenu: ILinkMenuItem[]
17
+ }
18
+
19
+ export interface ILinkMenuItem {
20
+ text: string,
21
+ url: string,
22
+ type: 'inbound' | 'outbound' | 'anchor'
23
+ active?: boolean
24
+ }
25
+
26
+ export interface ITitleLink {
27
+ url: string,
28
+ text?: string,
29
+ }
30
+
31
+
32
+ export interface ICtaItem {
33
+ text: string,
34
+ url: string,
35
+ variant?: Variant
36
+ linktype?: LinkType
37
+ className?: string,
38
+ }
39
+
40
+ export interface SubNavLabels {
41
+ openMainMenu: string
42
+ }
43
+
44
+ const defaultSubNavLabels: SubNavLabels = {
45
+ openMainMenu: 'Open main menu',
46
+ }
47
+
48
+ interface ISubNavProps {
49
+ titleLink?: ITitleLink,
50
+ titleContent?: React.JSX.Element,
51
+ ctaLinks: ICtaItem[],
52
+ hideGithubStars: boolean
53
+ menuItems: MenuItem[],
54
+ menuItemsAlign: 'left' | 'right',
55
+ className?: string,
56
+ labels?: Partial<SubNavLabels>
57
+ }
58
+
59
+ function Subnav({
60
+ className,
61
+ titleLink,
62
+ ctaLinks = [],
63
+ hideGithubStars,
64
+ menuItems,
65
+ menuItemsAlign = `right`,
66
+ titleContent,
67
+ labels: labelsProp
68
+ }: ISubNavProps) {
69
+ const labels = { ...defaultSubNavLabels, ...labelsProp }
70
+
71
+ const { isStuck, stuckRef } = useStuckRef([])
72
+
73
+ return (
74
+ <div className={clsx(style.root, `bg-white dark:bg-slate-900`, className, {
75
+ [style.isSticky]: isStuck,
76
+ })} ref={stuckRef}>
77
+ <Disclosure as="nav" >
78
+ {({ open }) => (
79
+ <>
80
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
81
+ <div className="flex justify-between h-16">
82
+ <div className={clsx(`flex grow`, menuItemsAlign == `right` ? ` justify-between` : ``)}>
83
+
84
+ <div className="flex-shrink-0 flex items-center ">
85
+ {titleLink &&<TitleLink
86
+ text={titleLink.text}
87
+
88
+ url={titleLink.url} >{titleContent}</TitleLink>}
89
+ {!titleLink && titleContent}
90
+ </div>
91
+ <div className="hidden sm:-my-px sm:ml-6 sm:flex">
92
+ <MenuItemsDefault
93
+ menuItems={menuItems}
94
+ />
95
+ <CtaLinks
96
+ hideGithubStars={hideGithubStars}
97
+ isInDropdown={false}
98
+ links={ctaLinks}
99
+ />
100
+ </div>
101
+ </div>
102
+
103
+ <div className="-mr-2 flex items-center sm:hidden ">
104
+ {/* Mobile menu button */}
105
+ <DisclosureButton className="bg-white inline-flex items-center justify-center p-2 rounded-control text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-secondary">
106
+ <span className="sr-only">{labels.openMainMenu}</span>
107
+ {open ? (
108
+ <XMarkIcon aria-hidden="true" className="block h-6 w-6" />
109
+ ) : (
110
+ <Bars3Icon aria-hidden="true" className="block h-6 w-6" />
111
+ )}
112
+ </DisclosureButton>
113
+ </div>
114
+ </div>
115
+
116
+ </div>
117
+
118
+ <DisclosurePanel className="sm:hidden">
119
+ <div className="pt-2 pb-3 space-y-1">
120
+ <MenuItemsOverflow
121
+ ctaLinks={ctaLinks}
122
+ hideGithubStars={hideGithubStars}
123
+ menuItems={menuItems}
124
+ /> </div>
125
+
126
+ </DisclosurePanel>
127
+ </>
128
+ )}
129
+ </Disclosure>
130
+ </div>
131
+ )
132
+ }
133
+
134
+ export default Subnav
@@ -1,25 +1,25 @@
1
- import formatStarCount from './index.js'
2
-
3
- describe('<Subnav /> - formatStarCount', () => {
4
- it('should return zero for an undefined, null, or zero count', () => {
5
- expect(formatStarCount(undefined)).toBe(false)
6
- expect(formatStarCount(null)).toBe(false)
7
- expect(formatStarCount(0)).toBe(false)
8
- })
9
-
10
- it('should return numbers 1 to 999 without formatting', () => {
11
- expect(formatStarCount(1)).toBe('1')
12
- expect(formatStarCount(999)).toBe('999')
13
- })
14
-
15
- it('should abbreviate numbers in the tens of thousands with "k" and a single decimal point', () => {
16
- expect(formatStarCount(1003)).toBe('1.0k')
17
- expect(formatStarCount(1280)).toBe('1.2k')
18
- expect(formatStarCount(15630)).toBe('15.6k')
19
- })
20
-
21
- it('should abbreviate numbers in the hundreds of thousands with "k" and no decimal point', () => {
22
- expect(formatStarCount(100000)).toBe('100k')
23
- expect(formatStarCount(156783)).toBe('156k')
24
- })
25
- })
1
+ import formatStarCount from './index.js'
2
+
3
+ describe('<Subnav /> - formatStarCount', () => {
4
+ it('should return zero for an undefined, null, or zero count', () => {
5
+ expect(formatStarCount(undefined)).toBe(false)
6
+ expect(formatStarCount(null)).toBe(false)
7
+ expect(formatStarCount(0)).toBe(false)
8
+ })
9
+
10
+ it('should return numbers 1 to 999 without formatting', () => {
11
+ expect(formatStarCount(1)).toBe('1')
12
+ expect(formatStarCount(999)).toBe('999')
13
+ })
14
+
15
+ it('should abbreviate numbers in the tens of thousands with "k" and a single decimal point', () => {
16
+ expect(formatStarCount(1003)).toBe('1.0k')
17
+ expect(formatStarCount(1280)).toBe('1.2k')
18
+ expect(formatStarCount(15630)).toBe('15.6k')
19
+ })
20
+
21
+ it('should abbreviate numbers in the hundreds of thousands with "k" and no decimal point', () => {
22
+ expect(formatStarCount(100000)).toBe('100k')
23
+ expect(formatStarCount(156783)).toBe('156k')
24
+ })
25
+ })
@@ -1,17 +1,17 @@
1
- /*
2
- * Given:
3
- * starCount (int)
4
- * Return:
5
- * Formatted string, to match GitHub's typical display of star counts,
6
- * that is, expressed as thousands of stars
7
- * Or returns false for falsy starCount values
8
- */
9
- function formatStarCount(starCount: number) {
10
- if (!starCount || starCount <= 0) return false
11
- if (starCount < 1000) return `${starCount}`
12
- const thousands = Math.floor(starCount / 100.0) / 10.0
13
- if (starCount < 100000) return `${thousands.toFixed(1)}k`
14
- return `${Math.floor(thousands)}k`
15
- }
16
-
17
- export default formatStarCount
1
+ /*
2
+ * Given:
3
+ * starCount (int)
4
+ * Return:
5
+ * Formatted string, to match GitHub's typical display of star counts,
6
+ * that is, expressed as thousands of stars
7
+ * Or returns false for falsy starCount values
8
+ */
9
+ function formatStarCount(starCount: number) {
10
+ if (!starCount || starCount <= 0) return false
11
+ if (starCount < 1000) return `${starCount}`
12
+ const thousands = Math.floor(starCount / 100.0) / 10.0
13
+ if (starCount < 100000) return `${thousands.toFixed(1)}k`
14
+ return `${Math.floor(thousands)}k`
15
+ }
16
+
17
+ export default formatStarCount
@@ -1,96 +1,96 @@
1
- "use client";
2
-
3
- import React, { useEffect, useState } from 'react'
4
- import type { IconObject } from '@dbosoft/react-uicore/linkbutton';
5
- import LinkButton from '@dbosoft/react-uicore/linkbutton';
6
- import { StarIcon } from '@heroicons/react/16/solid'
7
- import GithubIcon from '../icons/github.svg'
8
- import formatStarCount from './formatStarCount'
9
- import parseGithubUrl from './parseGithubUrl'
10
- import Link from 'next/link';
11
-
12
- export interface GithubStarsLinkLabels {
13
- github: string
14
- }
15
-
16
- const defaultGithubStarsLinkLabels: GithubStarsLinkLabels = {
17
- github: 'Github',
18
- }
19
-
20
- function GithubStarsButton({ url, hideGithubStars, labels: labelsProp }: { url: string, hideGithubStars: boolean, labels?: Partial<GithubStarsLinkLabels> }) {
21
- const labels = { ...defaultGithubStarsLinkLabels, ...labelsProp }
22
- const [starCount, setStarCount] = useState(-1)
23
-
24
- useEffect(() => {
25
- if (hideGithubStars) { setStarCount(0); return; }
26
- const parseResult = parseGithubUrl(url);
27
- const { org, repo } = parseResult !== false ? parseResult : { org: undefined, repo: undefined };
28
-
29
- if (!org || !repo) { setStarCount(0); return; }
30
- const githubApiUrl = `https://api.github.com/repos/${org}/${repo}`
31
- fetch(githubApiUrl)
32
- .then((response) => {
33
- response.json().then((data) => {
34
- // Github's rate limit for unauthenticated requests is 60 per hour
35
- // When the limit is hit, data.stargazers_count is undefined,
36
- // and setStarCount falls back to not showing the star count
37
- setStarCount(data.stargazers_count)
38
- // Warn if this limit is hit, to avoid otherwise confusing behavior
39
- // We're still using the response to provide a documentation link
40
- if (!data.stargazers_count) {
41
- const { headers } = response
42
- if (headers.get('x-ratelimit-remaining') === '0') {
43
- const resetAtSeconds = parseInt(headers.get('x-ratelimit-reset') || '')
44
- const resetDate = new Date(resetAtSeconds * 1000)
45
- const rateLimit = headers.get('x-ratelimit-limit')
46
- console.warn(
47
- `⭐ Stargazers count could not be fetched. Rate limit exceeded for unauthenticated GitHub API. Limit will be reset to ${rateLimit} at ${resetDate}. See ${data.documentation_url} for more details.`
48
- )
49
- } else {
50
- console.warn(
51
- `Request for stargazers was successful, but the returned value was undefined or falsy. This might be because the repo has no stars, or it might be a different issue.`
52
- )
53
- }
54
- }
55
- })
56
- })
57
- .catch((err) => {
58
- setStarCount(0)
59
- console.warn(JSON.stringify(err, null, 2))
60
- })
61
- }, [hideGithubStars, url])
62
-
63
- const isLoadingStarCount = starCount === -1
64
- const isFailedStarCount = formatStarCount(starCount) === false
65
-
66
- const showStarCount = !hideGithubStars && (isLoadingStarCount || !isFailedStarCount);
67
-
68
- const icon: IconObject = {
69
- position: 'right',
70
- svg: GithubIcon,
71
- }
72
-
73
- return (
74
- <LinkButton icon={!showStarCount ? icon : undefined} label={labels.github} text={!showStarCount ? labels.github : ""}
75
- variant='neutral'
76
- url={url} external
77
- Link={Link}
78
- >
79
- {showStarCount ? <div className="inline-flex">
80
- <GithubIcon className="w-5 mr-2" />
81
- <div className='-my-2 mx-2 h-9 block bg-gray-300' style={{ width: "1px" }} />
82
-
83
- <span className="inline-flex gap-1 items-center">
84
- <StarIcon className='w-5 fill-yellow-500' />
85
- <span className="g-type-body-small-strong" data-testid="github-stars">
86
- {formatStarCount(starCount) || <span>&mdash;</span>}
87
- </span>
88
- </span>
89
-
90
- </div> : null}
91
- </LinkButton>
92
-
93
- )
94
- }
95
-
96
- export default GithubStarsButton
1
+ "use client";
2
+
3
+ import React, { useEffect, useState } from 'react'
4
+ import type { IconObject } from '@dbosoft/react-uicore/linkbutton';
5
+ import LinkButton from '@dbosoft/react-uicore/linkbutton';
6
+ import { StarIcon } from '@heroicons/react/16/solid'
7
+ import GithubIcon from '../icons/github.svg'
8
+ import formatStarCount from './formatStarCount'
9
+ import parseGithubUrl from './parseGithubUrl'
10
+ import Link from 'next/link';
11
+
12
+ export interface GithubStarsLinkLabels {
13
+ github: string
14
+ }
15
+
16
+ const defaultGithubStarsLinkLabels: GithubStarsLinkLabels = {
17
+ github: 'Github',
18
+ }
19
+
20
+ function GithubStarsButton({ url, hideGithubStars, labels: labelsProp }: { url: string, hideGithubStars: boolean, labels?: Partial<GithubStarsLinkLabels> }) {
21
+ const labels = { ...defaultGithubStarsLinkLabels, ...labelsProp }
22
+ const [starCount, setStarCount] = useState(-1)
23
+
24
+ useEffect(() => {
25
+ if (hideGithubStars) { setStarCount(0); return; }
26
+ const parseResult = parseGithubUrl(url);
27
+ const { org, repo } = parseResult !== false ? parseResult : { org: undefined, repo: undefined };
28
+
29
+ if (!org || !repo) { setStarCount(0); return; }
30
+ const githubApiUrl = `https://api.github.com/repos/${org}/${repo}`
31
+ fetch(githubApiUrl)
32
+ .then((response) => {
33
+ response.json().then((data) => {
34
+ // Github's rate limit for unauthenticated requests is 60 per hour
35
+ // When the limit is hit, data.stargazers_count is undefined,
36
+ // and setStarCount falls back to not showing the star count
37
+ setStarCount(data.stargazers_count)
38
+ // Warn if this limit is hit, to avoid otherwise confusing behavior
39
+ // We're still using the response to provide a documentation link
40
+ if (!data.stargazers_count) {
41
+ const { headers } = response
42
+ if (headers.get('x-ratelimit-remaining') === '0') {
43
+ const resetAtSeconds = parseInt(headers.get('x-ratelimit-reset') || '')
44
+ const resetDate = new Date(resetAtSeconds * 1000)
45
+ const rateLimit = headers.get('x-ratelimit-limit')
46
+ console.warn(
47
+ `⭐ Stargazers count could not be fetched. Rate limit exceeded for unauthenticated GitHub API. Limit will be reset to ${rateLimit} at ${resetDate}. See ${data.documentation_url} for more details.`
48
+ )
49
+ } else {
50
+ console.warn(
51
+ `Request for stargazers was successful, but the returned value was undefined or falsy. This might be because the repo has no stars, or it might be a different issue.`
52
+ )
53
+ }
54
+ }
55
+ })
56
+ })
57
+ .catch((err) => {
58
+ setStarCount(0)
59
+ console.warn(JSON.stringify(err, null, 2))
60
+ })
61
+ }, [hideGithubStars, url])
62
+
63
+ const isLoadingStarCount = starCount === -1
64
+ const isFailedStarCount = formatStarCount(starCount) === false
65
+
66
+ const showStarCount = !hideGithubStars && (isLoadingStarCount || !isFailedStarCount);
67
+
68
+ const icon: IconObject = {
69
+ position: 'right',
70
+ svg: GithubIcon,
71
+ }
72
+
73
+ return (
74
+ <LinkButton icon={!showStarCount ? icon : undefined} label={labels.github} text={!showStarCount ? labels.github : ""}
75
+ variant='neutral'
76
+ url={url} external
77
+ Link={Link}
78
+ >
79
+ {showStarCount ? <div className="inline-flex">
80
+ <GithubIcon className="w-5 mr-2" />
81
+ <div className='-my-2 mx-2 h-9 block bg-gray-300' style={{ width: "1px" }} />
82
+
83
+ <span className="inline-flex gap-1 items-center">
84
+ <StarIcon className='w-5 fill-yellow-500' />
85
+ <span className="g-type-body-small-strong" data-testid="github-stars">
86
+ {formatStarCount(starCount) || <span>&mdash;</span>}
87
+ </span>
88
+ </span>
89
+
90
+ </div> : null}
91
+ </LinkButton>
92
+
93
+ )
94
+ }
95
+
96
+ export default GithubStarsButton
@@ -1,25 +1,25 @@
1
- import parseGithubUrl from './index.js'
2
-
3
- describe('<Subnav /> - parseGithubUrl', () => {
4
- it('should gracefully handle an invalid URL', () => {
5
- // Suppress console.warn for this test, we expect warnings
6
- jest.spyOn(console, 'warn')
7
- global.console.warn.mockImplementation(() => {})
8
- expect(parseGithubUrl(undefined)).toBe(false)
9
- expect(parseGithubUrl('blah-blah')).toBe(false)
10
- // Restore console.warn for further tests
11
- global.console.warn.mockRestore()
12
- })
13
-
14
- it('should handle a non-GitHub URL', () => {
15
- expect(parseGithubUrl('https://www.example.com/dbosoft/maxback')).toBe(
16
- false
17
- )
18
- })
19
-
20
- it('should parse the org and repo from a GitHub URL', () => {
21
- const testInput = 'https://www.github.com/dbosoft/maxback'
22
- expect(parseGithubUrl(testInput).repo).toBe('maxback')
23
- expect(parseGithubUrl(testInput).org).toBe('dbosoft')
24
- })
25
- })
1
+ import parseGithubUrl from './index.js'
2
+
3
+ describe('<Subnav /> - parseGithubUrl', () => {
4
+ it('should gracefully handle an invalid URL', () => {
5
+ // Suppress console.warn for this test, we expect warnings
6
+ jest.spyOn(console, 'warn')
7
+ global.console.warn.mockImplementation(() => {})
8
+ expect(parseGithubUrl(undefined)).toBe(false)
9
+ expect(parseGithubUrl('blah-blah')).toBe(false)
10
+ // Restore console.warn for further tests
11
+ global.console.warn.mockRestore()
12
+ })
13
+
14
+ it('should handle a non-GitHub URL', () => {
15
+ expect(parseGithubUrl('https://www.example.com/dbosoft/maxback')).toBe(
16
+ false
17
+ )
18
+ })
19
+
20
+ it('should parse the org and repo from a GitHub URL', () => {
21
+ const testInput = 'https://www.github.com/dbosoft/maxback'
22
+ expect(parseGithubUrl(testInput).repo).toBe('maxback')
23
+ expect(parseGithubUrl(testInput).org).toBe('dbosoft')
24
+ })
25
+ })
@@ -1,25 +1,25 @@
1
- /*
2
- * Given:
3
- * urlString (string) valid URL string
4
- * Return:
5
- * { org, repo }, where the url is ~= www.github.com/{org}/{repo}
6
- * or false otherwise (eg if not a valid URL, or if not a GitHub url)
7
- */
8
- function parseGithubUrl(urlString: string) {
9
- try {
10
- const urlObj = new URL(urlString)
11
- if (urlObj.hostname !== 'www.github.com') return false
12
- const parts = urlObj.pathname.split('/').filter(Boolean)
13
- const org = parts[0]
14
- const repo = parts[1]
15
- return { org, repo }
16
- } catch (err) {
17
- console.warn(
18
- 'Warning! An invalid URL has probably been supplied to the GitHub ctaLink in <Subnav />. The corresponding error:'
19
- )
20
- console.warn(err)
21
- return false
22
- }
23
- }
24
-
25
- export default parseGithubUrl
1
+ /*
2
+ * Given:
3
+ * urlString (string) valid URL string
4
+ * Return:
5
+ * { org, repo }, where the url is ~= www.github.com/{org}/{repo}
6
+ * or false otherwise (eg if not a valid URL, or if not a GitHub url)
7
+ */
8
+ function parseGithubUrl(urlString: string) {
9
+ try {
10
+ const urlObj = new URL(urlString)
11
+ if (urlObj.hostname !== 'www.github.com') return false
12
+ const parts = urlObj.pathname.split('/').filter(Boolean)
13
+ const org = parts[0]
14
+ const repo = parts[1]
15
+ return { org, repo }
16
+ } catch (err) {
17
+ console.warn(
18
+ 'Warning! An invalid URL has probably been supplied to the GitHub ctaLink in <Subnav />. The corresponding error:'
19
+ )
20
+ console.warn(err)
21
+ return false
22
+ }
23
+ }
24
+
25
+ export default parseGithubUrl