Skip to content

Commit

Permalink
add incident web share (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
myty authored Nov 12, 2022
1 parent 3700f54 commit 316698b
Show file tree
Hide file tree
Showing 7 changed files with 244 additions and 10 deletions.
2 changes: 1 addition & 1 deletion src/lanco-incidents-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"serve": "vite preview",
"coverage": "vitest run --coverage",
"coverage-report": "vitest run --coverage && codecov --disable=gcov",
"lint": "tsc --noEmit && eslint . --ext .js,.jsx,.ts,.tsx --fix && rome check --apply",
"lint": "tsc --noEmit && eslint . --ext .js,.jsx,.ts,.tsx --fix && rome check --apply ./src",
"test": "vitest"
},
"dependencies": {
Expand Down
12 changes: 12 additions & 0 deletions src/lanco-incidents-app/rome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"formatter": {
"indentStyle": "space",
"indentSize": 4
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
}
}
34 changes: 31 additions & 3 deletions src/lanco-incidents-app/src/components/page-title.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,32 @@ import React, { PropsWithChildren } from "react";

interface PageTitleProps {
onBack?: () => void;
onShare?: () => void;
showBackButton?: boolean;
showShareButton?: boolean;
}

const PageTitle: React.FC<PropsWithChildren<PageTitleProps>> = ({
children,
onBack,
onShare,
showBackButton = true,
showShareButton = false,
}) => {
return (
<div className="flex">
{showBackButton && (
<button
aria-label="Go Back"
className="relative -left-2 w-7"
onClick={() => onBack?.()}>
onClick={() => onBack?.()}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor">
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
Expand All @@ -31,7 +37,29 @@ const PageTitle: React.FC<PropsWithChildren<PageTitleProps>> = ({
</svg>
</button>
)}
<div>{children}</div>
<div className="flex-grow">{children}</div>
{showShareButton && (
<button
aria-label="Go Back"
className="w-7"
onClick={() => onShare?.()}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={2}
stroke="currentColor"
className="w-6 h-6"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M7.217 10.907a2.25 2.25 0 100 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186l9.566-5.314m-9.566 7.5l9.566 5.314m0 0a2.25 2.25 0 103.935 2.186 2.25 2.25 0 00-3.935-2.186zm0-12.814a2.25 2.25 0 103.933-2.185 2.25 2.25 0 00-3.933 2.185z"
/>
</svg>
</button>
)}
</div>
);
};
Expand Down
2 changes: 1 addition & 1 deletion src/lanco-incidents-app/src/containers/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export default function Layout({
className={`z-40 bg-blue-900 text-gray-50 ${headerShadowClass}`}
>
<div className="flex items-center h-full px-6 py-4 mx-auto">
<div className="flex-grow inline-block pr-6 text-lg font-semibold">
<div className="flex-grow inline-block text-lg font-semibold">
{headerLeft}
</div>
{isUpdating ? null : headerRight}
Expand Down
148 changes: 148 additions & 0 deletions src/lanco-incidents-app/src/hooks/use-web-share.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/**
* @vitest-environment jsdom
*/

import { act, renderHook, waitFor } from "@testing-library/react";
import { describe, it, expect, afterEach, vi, beforeEach, Mock } from "vitest";

import { useWebShare } from "hooks/use-web-share";

describe("useWebShare", () => {
it("initializes", async () => {
// Arrange, Act
const { result } = renderHook(() => {
return useWebShare();
});

// Assert
expect(result.current).toBeDefined();
expect(result.current).toBeTypeOf("object");
expect(result.current.isSharing).toBe(false);
expect(result.current.enabled).toBeDefined();
expect(result.current.error).toBeUndefined();
expect(result.current.share).toBeTypeOf("function");
});

describe("when no browser sharing capability", () => {
it("is not enabled", async () => {
// Arrange, Act
const { result } = renderHook(() => {
return useWebShare();
});

// Act
const { enabled } = result.current;

// Assert
expect(enabled).toBe(false);
});
});

describe("when browser has sharing capability", () => {
beforeEach(() => {
vi.stubGlobal("navigator", { share: vi.fn() });
});

afterEach(() => {
vi.resetAllMocks();
});

it("is enabled", async () => {
// Arrange, Act
const { result } = renderHook(() => {
return useWebShare();
});

// Act
const { enabled } = result.current;

// Assert
expect(enabled).toBe(true);
});
});

describe("share()", () => {
describe("when browser has sharing capability", () => {
afterEach(() => {
vi.resetAllMocks();
});

it("calls navigator.share", async () => {
// Arrange
const mockShare: Mock<any[], Promise<void>> = vi.fn(() =>
Promise.resolve()
);
vi.stubGlobal("navigator", { share: mockShare });
const { result } = renderHook(() => {
return useWebShare();
});

// Act
act(() => {
result.current.share({});
});

// Assert
expect(mockShare).toBeCalled();
});

describe("when successful", () => {
it("is updates isSharing", async () => {
// Arrange
vi.stubGlobal("navigator", {
share: () => Promise.resolve(),
});
const all: ReturnType<typeof useWebShare>[] = [];
const { result, rerender } = renderHook(() => {
const value = useWebShare();
all.push(value);
return value;
});

// Act
act(() => {
result.current.share({});
});

// Assert
expect(result.current.isSharing).toBe(true);

await waitFor(() => !result.current.isSharing);
rerender();

expect(result.current.isSharing).toBe(false);
});
});

describe("when errored", () => {
it("is updates error", async () => {
// Arrange
const errorMessage = "This is an error";
vi.stubGlobal("navigator", {
share: () => Promise.reject(errorMessage),
});
const all: ReturnType<typeof useWebShare>[] = [];
const { result, rerender } = renderHook(() => {
const value = useWebShare();
all.push(value);
return value;
});

// Act
act(() => {
result.current.share({});
});

// Assert
expect(result.current.isSharing).toBe(true);

await waitFor(() => !result.current.isSharing);
rerender();

expect(result.current.error).toBe(errorMessage);
expect(result.current.isSharing).toBe(false);
});
});
});
});
});
36 changes: 36 additions & 0 deletions src/lanco-incidents-app/src/hooks/use-web-share.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useState } from "react";

export function useWebShare() {
const [error, setError] = useState<string>();
const [isSharing, setIsSharing] = useState(false);
const enabled = window.navigator.share != null;

const share = ({ title, text, url }: Omit<ShareData, "files">) => {
if (!navigator.share) {
return;
}

setIsSharing(true);

navigator
.share({ title, text, url })
.catch((error) => {
const errorMessage: string =
error instanceof Error
? error.message
: typeof error === "string"
? error
: error?.toString();

setError(errorMessage);
})
.finally(() => setIsSharing(false));
};

return {
isSharing,
enabled,
error,
share,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,34 @@ import { IncidentDetailContent } from "./incident-detail-content";
import PageTitle from "components/page-title";
import { IncidentRecord } from "models/view-models/incident-record";
import useIncident from "hooks/use-incident";
import { useWebShare } from "hooks/use-web-share";

interface IncidentDetailProps {}

export default function IncidentDetail(props: IncidentDetailProps) {
export default function IncidentDetail() {
const { id } = useParams<"id">();
const navigate = useNavigate();
const { incident } = useIncident({ id });
const webShare = useWebShare();

const handleGoBack = () => navigate(-1);

const handleShare = () =>
webShare.share({
url: window.location.href,
});

return (
<Layout
pageBgStyle="bg-white"
headerLeft={
<PageTitle onBack={handleGoBack}>
<PageTitle
onBack={handleGoBack}
showShareButton={webShare.enabled}
onShare={handleShare}
>
{getTitle(incident)}
</PageTitle>
}>
}
>
<IncidentDetailContent incident={incident} />
</Layout>
);
Expand Down

1 comment on commit 316698b

@vercel
Copy link

@vercel vercel bot commented on 316698b Nov 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.