Compare commits
5 Commits
841977b02e
...
591f7f2af6
Author | SHA1 | Date |
---|---|---|
Dmitriy Pleshevskiy | 591f7f2af6 | |
Dmitriy Pleshevskiy | cbf8132b32 | |
Dmitriy Pleshevskiy | d87a5abb6d | |
Dmitriy Pleshevskiy | 2b243abb82 | |
Dmitriy Pleshevskiy | 7c4098b2dc |
|
@ -0,0 +1,41 @@
|
||||||
|
// https://docs.cypress.io/api/introduction/api.html
|
||||||
|
|
||||||
|
import { Page } from "../support/pages";
|
||||||
|
|
||||||
|
describe("Contacts", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit(Page.Contacts);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should open contacts page", () => {
|
||||||
|
cy.contains("h1", "Контакты");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should load contacts table", () => {
|
||||||
|
cy.get("[cy-test=contacts-table-col-email]")
|
||||||
|
.should("have.length.gte", 20)
|
||||||
|
.first()
|
||||||
|
.should("have.text", "brendon.cremin91@gmail.com");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should load lists select", () => {
|
||||||
|
cy.get("[cy-test=lists-select]")
|
||||||
|
.should("have.value", "")
|
||||||
|
.within(() => cy.get("option").should("have.length.gte", 20));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should load contacts of selected list", () => {
|
||||||
|
cy.get("[cy-test=lists-select]")
|
||||||
|
.should("have.value", "")
|
||||||
|
.select("c407a7bd-7930-43bc-8241-2aafd058ed39");
|
||||||
|
|
||||||
|
cy.get("[cy-test=contacts-table-col-email]")
|
||||||
|
.first()
|
||||||
|
.should("have.text", "camryn90@yahoo.com");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should open create contacts page", () => {
|
||||||
|
cy.get("[cy-test=openCreateContactForm-button]").click();
|
||||||
|
cy.location("pathname").should("eq", Page.CreateContact);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { Page } from "../../support/pages";
|
||||||
|
|
||||||
|
describe("Create contact", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit(Page.CreateContact);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create a contact", () => {
|
||||||
|
const email = "foo@example.com";
|
||||||
|
|
||||||
|
cy.get("[cy-test=createContact-form-field-email]").type(email);
|
||||||
|
cy.get("[cy-test=createContact-button]").click();
|
||||||
|
|
||||||
|
cy.location("pathname").should("eq", Page.Contacts);
|
||||||
|
|
||||||
|
cy.get("[cy-test=contacts-table-col-email]")
|
||||||
|
.first()
|
||||||
|
.should("have.text", email);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { Page } from "../support/pages";
|
||||||
|
|
||||||
|
describe("Dashboard", () => {
|
||||||
|
it("should redirect to contacts page", () => {
|
||||||
|
cy.visit(Page.Dashboard);
|
||||||
|
cy.location("pathname").should("eq", Page.Contacts);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,8 +0,0 @@
|
||||||
// https://docs.cypress.io/api/introduction/api.html
|
|
||||||
|
|
||||||
describe("My First Test", () => {
|
|
||||||
it("visits the app root url", () => {
|
|
||||||
cy.visit("/");
|
|
||||||
cy.contains("h1", "You did it!");
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
export enum Page {
|
||||||
|
Dashboard = "/",
|
||||||
|
Contacts = "/contacts",
|
||||||
|
CreateContact = "/contacts/new",
|
||||||
|
}
|
43
flake.nix
43
flake.nix
|
@ -7,17 +7,42 @@
|
||||||
outputs = { self, nixpkgs, flake-utils, ... }:
|
outputs = { self, nixpkgs, flake-utils, ... }:
|
||||||
flake-utils.lib.eachDefaultSystem (system:
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
let
|
let
|
||||||
pkgs = import nixpkgs { inherit system; };
|
cypressOverlay = final: prev: {
|
||||||
|
cypress = prev.cypress.overrideAttrs (oldAttrs: rec {
|
||||||
|
version = "12.3.0";
|
||||||
|
|
||||||
|
src = prev.fetchzip {
|
||||||
|
url = "https://cdn.cypress.io/desktop/${version}/linux-x64/cypress.zip";
|
||||||
|
sha256 = "sha256-RhPH/MBF8lqXeFEm2sd73Z55jgcl45VsmRWtAhckrP0=";
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
overlays = [ cypressOverlay ];
|
||||||
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
devShells.default = pkgs.mkShell {
|
devShells = rec {
|
||||||
packages = with pkgs; [
|
code = pkgs.mkShell {
|
||||||
nodejs-18_x
|
packages = with pkgs; [
|
||||||
nodePackages.vue-cli
|
nodejs-18_x
|
||||||
nodePackages.vls # vue
|
nodePackages.vue-cli
|
||||||
nodePackages.typescript-language-server # typescript
|
nodePackages.vls # vue
|
||||||
nodePackages.vscode-langservers-extracted # html, css, json, eslint
|
nodePackages.typescript-language-server # typescript
|
||||||
];
|
nodePackages.vscode-langservers-extracted # html, css, json, eslint
|
||||||
|
];
|
||||||
|
};
|
||||||
|
e2e = pkgs.mkShell {
|
||||||
|
shellHook = ''
|
||||||
|
export CYPRESS_INSTALL_BINARY=0;
|
||||||
|
export CYPRESS_RUN_BINARY="${pkgs.cypress}/bin/Cypress";
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
default = pkgs.mkShell {
|
||||||
|
inputsFrom = [ code e2e ];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
"@vue/eslint-config-typescript": "^11.0.0",
|
"@vue/eslint-config-typescript": "^11.0.0",
|
||||||
"@vue/test-utils": "^2.2.6",
|
"@vue/test-utils": "^2.2.6",
|
||||||
"@vue/tsconfig": "^0.1.3",
|
"@vue/tsconfig": "^0.1.3",
|
||||||
"cypress": "^12.0.2",
|
"cypress": "^12.3.0",
|
||||||
"eslint": "^8.22.0",
|
"eslint": "^8.22.0",
|
||||||
"eslint-plugin-cypress": "^2.12.1",
|
"eslint-plugin-cypress": "^2.12.1",
|
||||||
"eslint-plugin-vue": "^9.3.0",
|
"eslint-plugin-vue": "^9.3.0",
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
"@vue/eslint-config-typescript": "^11.0.0",
|
"@vue/eslint-config-typescript": "^11.0.0",
|
||||||
"@vue/test-utils": "^2.2.6",
|
"@vue/test-utils": "^2.2.6",
|
||||||
"@vue/tsconfig": "^0.1.3",
|
"@vue/tsconfig": "^0.1.3",
|
||||||
"cypress": "^12.0.2",
|
"cypress": "^12.3.0",
|
||||||
"eslint": "^8.22.0",
|
"eslint": "^8.22.0",
|
||||||
"eslint-plugin-cypress": "^2.12.1",
|
"eslint-plugin-cypress": "^2.12.1",
|
||||||
"eslint-plugin-vue": "^9.3.0",
|
"eslint-plugin-vue": "^9.3.0",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import TheHeader from "@/app/common/components/TheHeader.vue";
|
import TheHeader from "@/app/misc/TheHeader.vue";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, defineEmits } from "vue";
|
import { ref } from "vue";
|
||||||
import { ContactFormApiCreateProps } from "./ports";
|
import { ContactFormApiCreateProps } from "./ports";
|
||||||
import { useContactFormStore } from "./store";
|
import { useContactFormStore } from "./store";
|
||||||
|
|
||||||
|
@ -22,9 +22,13 @@ async function submitForm() {
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<label>Email</label>
|
<label>Email</label>
|
||||||
<input v-model="contactData.email" />
|
<input
|
||||||
|
cy-test="createContact-formField-email"
|
||||||
|
v-model="contactData.email"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
|
cy-test="createContact"
|
||||||
:isPrimary="true"
|
:isPrimary="true"
|
||||||
:isLoading="store.loading"
|
:isLoading="store.loading"
|
||||||
@click="submitForm"
|
@click="submitForm"
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { useLoader } from "@/shared/lib/composables/loader";
|
import { useLoader } from "@/shared/lib/composables/loader";
|
||||||
import { defineInjectKey, validInject } from "@/shared/lib/di";
|
import { defineInjectKey, validInject } from "@/shared/lib/di";
|
||||||
import { reactive } from "vue";
|
import { defineStore } from "pinia";
|
||||||
import { useContactsStore } from "../store";
|
import { useContactsStore } from "../store";
|
||||||
import type { ContactFormApiCreateProps, ContactFormApiPort } from "./ports";
|
import type { ContactFormApiCreateProps, ContactFormApiPort } from "./ports";
|
||||||
|
|
||||||
export const CONTACT_FORM_API_PROVIDE_KEY =
|
export const CONTACT_FORM_API_PROVIDE_KEY =
|
||||||
defineInjectKey<ContactFormApiPort>("ContactFormApi");
|
defineInjectKey<ContactFormApiPort>("ContactFormApi");
|
||||||
|
|
||||||
export function useContactFormStore() {
|
export const useContactFormStore = defineStore("ContactForm", () => {
|
||||||
const api = validInject(CONTACT_FORM_API_PROVIDE_KEY);
|
const api = validInject(CONTACT_FORM_API_PROVIDE_KEY);
|
||||||
|
|
||||||
const contactsStore = useContactsStore();
|
const contactsStore = useContactsStore();
|
||||||
|
@ -18,8 +18,8 @@ export function useContactFormStore() {
|
||||||
contactsStore.addContact(contact);
|
contactsStore.addContact(contact);
|
||||||
}
|
}
|
||||||
|
|
||||||
return reactive({
|
return {
|
||||||
loading: loader.loading,
|
loading: loader.loading,
|
||||||
create,
|
create,
|
||||||
});
|
};
|
||||||
}
|
});
|
||||||
|
|
|
@ -28,6 +28,7 @@ watch(
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BaseTable
|
<BaseTable
|
||||||
|
cy-test="contacts"
|
||||||
:loading="store.loading"
|
:loading="store.loading"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:dataItems="store.contacts"
|
:dataItems="store.contacts"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { useLoader } from "@/shared/lib/composables/loader";
|
import { useLoader } from "@/shared/lib/composables/loader";
|
||||||
import { defineInjectKey, validInject } from "@/shared/lib/di";
|
import { defineInjectKey, validInject } from "@/shared/lib/di";
|
||||||
import { storeToRefs } from "pinia";
|
import { defineStore, storeToRefs } from "pinia";
|
||||||
import { reactive } from "vue";
|
import { reactive } from "vue";
|
||||||
import { useContactsStore } from "../store";
|
import { useContactsStore } from "../store";
|
||||||
import type {
|
import type {
|
||||||
|
@ -11,7 +11,7 @@ import type {
|
||||||
export const CONTACTS_TABLE_API_PROVIDE_KEY =
|
export const CONTACTS_TABLE_API_PROVIDE_KEY =
|
||||||
defineInjectKey<ContactsTableApiPort>("ContactsTableApi");
|
defineInjectKey<ContactsTableApiPort>("ContactsTableApi");
|
||||||
|
|
||||||
export function useContactsTableStore() {
|
export const useContactsTableStore = defineStore("ContactsTable", () => {
|
||||||
const api = validInject(CONTACTS_TABLE_API_PROVIDE_KEY);
|
const api = validInject(CONTACTS_TABLE_API_PROVIDE_KEY);
|
||||||
|
|
||||||
const contactsStore = useContactsStore();
|
const contactsStore = useContactsStore();
|
||||||
|
@ -23,9 +23,9 @@ export function useContactsTableStore() {
|
||||||
contactsStore.setContacts(contacts);
|
contactsStore.setContacts(contacts);
|
||||||
}
|
}
|
||||||
|
|
||||||
return reactive({
|
return {
|
||||||
loading: loader.loading,
|
loading: loader.loading,
|
||||||
contacts,
|
contacts,
|
||||||
fetchMany,
|
fetchMany,
|
||||||
});
|
};
|
||||||
}
|
});
|
||||||
|
|
|
@ -32,5 +32,5 @@ store.fetchMany();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BaseSelect v-model="value" :items="selectListItems" />
|
<BaseSelect cy-test="lists" v-model="value" :items="selectListItems" />
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import { useLoader } from "@/shared/lib/composables/loader";
|
import { useLoader } from "@/shared/lib/composables/loader";
|
||||||
import { defineInjectKey, validInject } from "@/shared/lib/di";
|
import { defineInjectKey, validInject } from "@/shared/lib/di";
|
||||||
import { computed, reactive } from "vue";
|
import { defineStore } from "pinia";
|
||||||
|
import { computed } from "vue";
|
||||||
import { useListsStore } from "../store";
|
import { useListsStore } from "../store";
|
||||||
import type { ListsSelectApiPort } from "./ports";
|
import type { ListsSelectApiPort } from "./ports";
|
||||||
|
|
||||||
export const LISTS_SELECT_API_PROVIDE_KEY =
|
export const LISTS_SELECT_API_PROVIDE_KEY =
|
||||||
defineInjectKey<ListsSelectApiPort>("ListsTableApi");
|
defineInjectKey<ListsSelectApiPort>("ListsSelectApi");
|
||||||
|
|
||||||
export function useListsSelectStore() {
|
export const useListsSelectStore = defineStore("ListsSelect", () => {
|
||||||
const api = validInject(LISTS_SELECT_API_PROVIDE_KEY);
|
const api = validInject(LISTS_SELECT_API_PROVIDE_KEY);
|
||||||
|
|
||||||
const listsStore = useListsStore();
|
const listsStore = useListsStore();
|
||||||
|
@ -25,9 +26,9 @@ export function useListsSelectStore() {
|
||||||
listsStore.setLists(lists);
|
listsStore.setLists(lists);
|
||||||
}
|
}
|
||||||
|
|
||||||
return reactive({
|
return {
|
||||||
loading: loader.loading,
|
loading: loader.loading,
|
||||||
selectItems,
|
selectItems,
|
||||||
fetchMany,
|
fetchMany,
|
||||||
});
|
};
|
||||||
}
|
});
|
||||||
|
|
|
@ -52,7 +52,9 @@ provide(LISTS_SELECT_API_PROVIDE_KEY, listApi);
|
||||||
<BasePageHeader>
|
<BasePageHeader>
|
||||||
Контакты
|
Контакты
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<BaseButton @click="openAddContactForm">Добавить контакт</BaseButton>
|
<BaseButton cy-test="openCreateContactForm" @click="openAddContactForm">
|
||||||
|
Добавить контакт
|
||||||
|
</BaseButton>
|
||||||
</template>
|
</template>
|
||||||
</BasePageHeader>
|
</BasePageHeader>
|
||||||
<AudienceListsSelect
|
<AudienceListsSelect
|
||||||
|
|
|
@ -7,7 +7,7 @@ const router = createRouter({
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
name: "dashboard",
|
name: "dashboard",
|
||||||
redirect: { name: "audience" },
|
redirect: { name: "audience_contacts" },
|
||||||
},
|
},
|
||||||
...audienceRoutes,
|
...audienceRoutes,
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
defineProps<{
|
defineProps({
|
||||||
isPrimary: boolean;
|
cyTest: { type: String, default: "base" },
|
||||||
isLoading: boolean;
|
isPrimary: Boolean,
|
||||||
}>();
|
isLoading: Boolean,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
:class="{ '--primary': isPrimary, '--loading': isLoading }"
|
:class="{ '--primary': isPrimary, '--loading': isLoading }"
|
||||||
|
:cy-test="`${cyTest}-button`"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -8,6 +8,7 @@ interface SelectItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
cyTest: { type: String, default: "base" },
|
||||||
items: {
|
items: {
|
||||||
type: Array as PropType<SelectItem[]>,
|
type: Array as PropType<SelectItem[]>,
|
||||||
required: true,
|
required: true,
|
||||||
|
@ -29,7 +30,7 @@ const value = computed({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<select v-model="value">
|
<select v-model="value" :cy-test="`${cyTest}-select`">
|
||||||
<option
|
<option
|
||||||
v-for="selectItem in items"
|
v-for="selectItem in items"
|
||||||
:key="selectItem.value"
|
:key="selectItem.value"
|
||||||
|
|
|
@ -1,29 +1,28 @@
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent, PropType } from "vue";
|
import { PropType } from "vue";
|
||||||
|
|
||||||
interface TableColumn {
|
interface TableColumn {
|
||||||
key: string;
|
key: string;
|
||||||
title: string;
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
defineProps({
|
||||||
props: {
|
cyTest: { type: String, default: "base" },
|
||||||
loading: Boolean,
|
loading: Boolean,
|
||||||
columns: {
|
columns: {
|
||||||
type: Array as PropType<TableColumn[]>,
|
type: Array as PropType<TableColumn[]>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
dataItems: {
|
dataItems: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="wrapper" :class="{ '--loading': loading }">
|
<div class="wrapper" :class="{ '--loading': loading }">
|
||||||
<table>
|
<table :cy-test="`${cyTest}-table`">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th v-for="column in columns" :key="column.key">
|
<th v-for="column in columns" :key="column.key">
|
||||||
|
@ -32,8 +31,16 @@ export default defineComponent({
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="(dataItem, index) in dataItems" :key="dataItem.id || index">
|
<tr
|
||||||
<td v-for="column in columns" :key="column.key">
|
v-for="(dataItem, index) in dataItems"
|
||||||
|
:key="dataItem.id || index"
|
||||||
|
:cy-test="`${cyTest}-table-row`"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
v-for="column in columns"
|
||||||
|
:key="column.key"
|
||||||
|
:cy-test="`${cyTest}-table-col-${column.key}`"
|
||||||
|
>
|
||||||
<slot :name="column.key" :dataItem="dataItem">{{
|
<slot :name="column.key" :dataItem="dataItem">{{
|
||||||
dataItem[column.key]
|
dataItem[column.key]
|
||||||
}}</slot>
|
}}</slot>
|
||||||
|
|
Loading…
Reference in New Issue