Co-authored-by: Camille Moussu <cmoussu@linagora.com> Co-authored-by: Benoit TELLIER <btellier@linagora.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
||||
addSharedCalendar,
|
||||
getCalendar,
|
||||
getCalendars,
|
||||
getSecretLink,
|
||||
postCalendar,
|
||||
proppatchCalendar,
|
||||
removeCalendar,
|
||||
@@ -114,6 +115,40 @@ describe("Calendar API", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("get secret link ", async () => {
|
||||
const calLink = "/calendars/calId.json";
|
||||
(api.get as jest.Mock).mockReturnValue({
|
||||
json: jest.fn().mockResolvedValue("link"),
|
||||
});
|
||||
|
||||
const noreset = await getSecretLink(calLink, false);
|
||||
|
||||
expect(api.get).toHaveBeenCalledWith(
|
||||
`calendar/api${calLink}/secret-link?shouldResetLink=false`,
|
||||
{
|
||||
headers: {
|
||||
Accept: "application/json, text/plain, */*",
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
it("get secret link ", async () => {
|
||||
const calLink = "/calendars/calId.json";
|
||||
(api.get as jest.Mock).mockReturnValue({
|
||||
json: jest.fn().mockResolvedValue("link"),
|
||||
});
|
||||
const reset = await getSecretLink(calLink, true);
|
||||
|
||||
expect(api.get).toHaveBeenCalledWith(
|
||||
`calendar/api${calLink}/secret-link?shouldResetLink=true`,
|
||||
{
|
||||
headers: {
|
||||
Accept: "application/json, text/plain, */*",
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("When adding a sharedCal with #default as a name a new name is sent to the back", async () => {
|
||||
const mockApiPost = jest.spyOn(api, "post");
|
||||
|
||||
|
||||
@@ -3,6 +3,11 @@ import CalendarPopover from "../../../src/components/Calendar/CalendarModal";
|
||||
import { renderWithProviders } from "../../utils/Renderwithproviders";
|
||||
import * as eventThunks from "../../../src/features/Calendars/CalendarSlice";
|
||||
import { Calendars } from "../../../src/features/Calendars/CalendarTypes";
|
||||
import { getSecretLink } from "../../../src/features/Calendars/CalendarApi";
|
||||
|
||||
jest.mock("../../../src/features/Calendars/CalendarApi", () => ({
|
||||
getSecretLink: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("CalendarPopover", () => {
|
||||
const mockOnClose = jest.fn();
|
||||
@@ -308,6 +313,9 @@ describe("CalendarPopover - Tabs Scenarios", () => {
|
||||
Object.assign(navigator, {
|
||||
clipboard: { writeText: jest.fn() },
|
||||
});
|
||||
(getSecretLink as jest.Mock).mockResolvedValue({
|
||||
secretLink: "https://example.org/secret/initial",
|
||||
});
|
||||
|
||||
renderWithProviders(
|
||||
<CalendarPopover
|
||||
@@ -326,7 +334,7 @@ describe("CalendarPopover - Tabs Scenarios", () => {
|
||||
expect(input).toHaveValue("https://cal.example.org/calendars/user1/cal1");
|
||||
|
||||
// Click copy button (find button containing ContentCopyIcon)
|
||||
const copyIcon = screen.getByTestId("ContentCopyIcon");
|
||||
const copyIcon = screen.getAllByTestId("ContentCopyIcon")[0];
|
||||
const copyButton = copyIcon.closest("button");
|
||||
if (copyButton) {
|
||||
fireEvent.click(copyButton);
|
||||
@@ -433,4 +441,46 @@ describe("CalendarPopover - Tabs Scenarios", () => {
|
||||
expect(importButton).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
it("fetches and resets the secret link", async () => {
|
||||
(window as any).CALENDAR_BASE_URL = "https://cal.example.org";
|
||||
|
||||
(getSecretLink as jest.Mock)
|
||||
.mockResolvedValueOnce({
|
||||
secretLink: "https://example.org/secret/initial",
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
secretLink: "https://example.org/secret/new",
|
||||
});
|
||||
|
||||
renderWithProviders(
|
||||
<CalendarPopover
|
||||
open={true}
|
||||
onClose={mockOnClose}
|
||||
calendar={existingCalendar}
|
||||
/>,
|
||||
{ user: baseUser }
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("tab", { name: /Access/i }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByDisplayValue("https://example.org/secret/initial")
|
||||
).toBeInTheDocument()
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /reset/i }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByDisplayValue("https://example.org/secret/new")
|
||||
).toBeInTheDocument()
|
||||
);
|
||||
|
||||
expect(getSecretLink).toHaveBeenCalledWith(
|
||||
existingCalendar.link.replace(".json", ""),
|
||||
true
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,39 +1,102 @@
|
||||
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
|
||||
import { Box, IconButton, TextField } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
IconButton,
|
||||
TextField,
|
||||
Button,
|
||||
Typography,
|
||||
InputAdornment,
|
||||
} from "@mui/material";
|
||||
import { useState, useEffect } from "react";
|
||||
import { getSecretLink } from "../../features/Calendars/CalendarApi";
|
||||
import { Calendars } from "../../features/Calendars/CalendarTypes";
|
||||
import { FieldWithLabel } from "../Event/components/FieldWithLabel";
|
||||
import { SnackbarAlert } from "../Loading/SnackBarAlert";
|
||||
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
|
||||
|
||||
export function AccessTab({ calendar }: { calendar: Calendars }) {
|
||||
const { t } = useI18n();
|
||||
const calDAVLink = `${(window as any).CALENDAR_BASE_URL}${calendar.link.replace(".json", "")}`;
|
||||
|
||||
const [secretLink, setSecretLink] = useState("");
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const handleCopy = () => {
|
||||
navigator.clipboard.writeText(calDAVLink);
|
||||
useEffect(() => {
|
||||
async function fetchSecret() {
|
||||
const existing = await getSecretLink(
|
||||
calendar.link.replace(".json", ""),
|
||||
false
|
||||
);
|
||||
setSecretLink(existing.secretLink);
|
||||
}
|
||||
fetchSecret();
|
||||
}, [calendar.link]);
|
||||
|
||||
const handleCopy = (content: string) => {
|
||||
navigator.clipboard.writeText(content);
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
const handleResetSecretLink = async () => {
|
||||
const newSecret = await getSecretLink(
|
||||
calendar.link.replace(".json", ""),
|
||||
true
|
||||
);
|
||||
setSecretLink(newSecret.secretLink);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box mt={2}>
|
||||
<TextField
|
||||
disabled
|
||||
fullWidth
|
||||
label={t("calendar.caldav_access")}
|
||||
value={calDAVLink}
|
||||
slotProps={{
|
||||
input: {
|
||||
<FieldWithLabel label={t("calendar.caldav_access")} isExpanded={false}>
|
||||
<Box mt={2}>
|
||||
<TextField
|
||||
disabled
|
||||
fullWidth
|
||||
label={t("calendar.caldav_access")}
|
||||
value={calDAVLink}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<IconButton onClick={handleCopy} edge="end">
|
||||
<ContentCopyIcon />
|
||||
</IconButton>
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={() => handleCopy(calDAVLink)} edge="end">
|
||||
<ContentCopyIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</FieldWithLabel>
|
||||
|
||||
<FieldWithLabel label={t("calendar.secretUrl")} isExpanded={false}>
|
||||
<Box mt={3} display="flex" alignItems="center" gap={1}>
|
||||
<TextField
|
||||
disabled
|
||||
fullWidth
|
||||
label={t("calendar.secretUrl")}
|
||||
value={secretLink}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={() => handleCopy(secretLink)} edge="end">
|
||||
<ContentCopyIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Button variant="outlined" onClick={handleResetSecretLink}>
|
||||
{t("actions.reset")}
|
||||
</Button>
|
||||
</Box>
|
||||
</FieldWithLabel>
|
||||
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ color: "text.secondary", mt: 1, lineHeight: 1.5 }}
|
||||
>
|
||||
{t("calendar.secretUrlDesc")}
|
||||
</Typography>
|
||||
|
||||
<SnackbarAlert
|
||||
setOpen={setOpen}
|
||||
open={open}
|
||||
|
||||
@@ -128,3 +128,14 @@ export async function updateAclCalendar(calLink: string, request: string) {
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
export async function getSecretLink(calLink: string, reset: boolean) {
|
||||
const response = await api
|
||||
.get(`calendar/api${calLink}/secret-link?shouldResetLink=${reset}`, {
|
||||
headers: {
|
||||
Accept: "application/json, text/plain, */*",
|
||||
},
|
||||
})
|
||||
.json();
|
||||
return response as { secretLink: string };
|
||||
}
|
||||
|
||||
+4
-1
@@ -5,6 +5,8 @@
|
||||
"delegated": "Delegated Calendars",
|
||||
"other": "Other Calendars",
|
||||
"caldav_access": "CalDAV access",
|
||||
"secretUrl": "Secret URL",
|
||||
"secretUrlDesc": "The secret address can be used to view all calendar events even without access permissions. You can reset your current secret address and get a new one.",
|
||||
"import_file_description": "Import events from an ICS file to one of your calendars.",
|
||||
"ics_feed_url": "ICS feed URL",
|
||||
"import_to": "Import to",
|
||||
@@ -28,7 +30,8 @@
|
||||
"save": "Save",
|
||||
"create": "Create",
|
||||
"import": "Import",
|
||||
"add": "Add"
|
||||
"add": "Add",
|
||||
"reset": "Reset"
|
||||
},
|
||||
"common": {
|
||||
"cancel": "Cancel",
|
||||
|
||||
+4
-1
@@ -5,6 +5,8 @@
|
||||
"delegated": "Calendriers délégués",
|
||||
"other": "Autres calendriers",
|
||||
"caldav_access": "Accès CalDAV",
|
||||
"secretUrl": "URL secrète",
|
||||
"secretUrlDesc": "L'adresse secrète peut être utilisée pour visualiser tous vos calendiers même sans permissions. Vous pouvez la réinitialiser pour en obtenir une nouvelle.",
|
||||
"import_file_description": "Importer des événements depuis un fichier ICS vers l'un de vos calendriers.",
|
||||
"ics_feed_url": "URL du flux ICS",
|
||||
"import_to": "Importer vers",
|
||||
@@ -28,7 +30,8 @@
|
||||
"save": "Enregistrer",
|
||||
"create": "Créer",
|
||||
"import": "Importer",
|
||||
"add": "Ajouter"
|
||||
"add": "Ajouter",
|
||||
"reset": "Réinitialiser"
|
||||
},
|
||||
"common": {
|
||||
"cancel": "Annuler",
|
||||
|
||||
Reference in New Issue
Block a user