diff --git a/.circleci/config.yml b/.circleci/config.yml
index ab1a6f7e..c6df2607 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -3,13 +3,12 @@ orbs:
browser-tools: circleci/browser-tools@1.4.1
jobs: # a collection of steps
- build: # runs not using Workflows must have a `build` job as entry point
+ dusk: # runs not using Workflows must have a `build` job as entry point
docker: # run the steps with Docker
- #- image: cimg/php:8.0.14-browsers # ...with this image as the primary container; this is where all `steps` will run
- image: cimg/php:8.2.4-browsers # ...with this image as the primary container; this is where all `steps` will run
auth:
username: mydockerhub-user
- password: $DOCKERHUB_PASSWORD # context / project UI env-var reference
+ password: $DOCKERHUB_PASSWORD # context / project UI env-var reference
working_directory: ~/laravel # directory where steps will run
steps: # a set of executable commands
- checkout # special step to check out source code to working directory
@@ -45,9 +44,9 @@ jobs: # a collection of steps
- run:
name: Serve Application
background: true
- command: php artisan serve
+ command: php artisan serve
- run: php artisan dusk
- - run: php artisan test
+ - run: php artisan test
- store_artifacts:
path: ./tests/Browser/console
destination: console
@@ -55,4 +54,28 @@ jobs: # a collection of steps
path: ./tests/Browser/screenshots
destination: screenshots
# See https://circleci.com/docs/2.0/deployment-integrations/ for deploy examples
- resource_class: large
+ resource_class: large
+
+ vue-tests:
+ docker:
+ - image: cimg/node:16.5.0
+ working_directory: ~/laravel
+ steps:
+ - checkout
+ - restore_cache:
+ keys:
+ - node-v1-{{ checksum "package-lock.json" }}
+ - node-v1-
+ - run: npm ci
+ - save_cache:
+ key: node-v1-{{ checksum "package-lock.json" }}
+ paths:
+ - node_modules
+ - run: npm run test # Assuming you have a "test" script in your package.json for running Vue tests
+
+workflows:
+ version: 2
+ build-and-test:
+ jobs:
+ - dusk
+ - vue-tests
diff --git a/README.md b/README.md
index 08bd10fa..6219344b 100644
--- a/README.md
+++ b/README.md
@@ -42,7 +42,7 @@ Ecommerce site with Laravel 10, Vue 3 and Stripe.
- SonarCloud code quality scanner integration on all pull requests
-- Laravel tests with CircleCI integration
+- Laravel Dusk and Jest tests with CircleCI integration
## Main dependencies:
@@ -101,8 +101,6 @@ Ecommerce site with Laravel 10, Vue 3 and Stripe.
- Do WCAG analysis and ensure there are no issues
-- Add some tests to verify that the cart and checkout works correctly
-
- Consider adding an admin dashboard
- Look into performance optimization
diff --git a/tests/Browser/ExampleTest.php b/tests/Browser/ExampleTest.php
index 2ce62b62..841ebba9 100644
--- a/tests/Browser/ExampleTest.php
+++ b/tests/Browser/ExampleTest.php
@@ -16,9 +16,8 @@ public function testBasicExample()
{
$this->browse(function (Browser $browser) {
$browser->visit('/')
- //->screenshot('home-page')
- ->assertSee('MacBook');
- //->assertPathIs('/');
+ ->assertSee('MacBook')
+ ->assertPathIs('/');
});
}
}
diff --git a/tests/Vue/Button/button.spec.js b/tests/Vue/Button/button.spec.js
index 26f3573a..67d6158f 100644
--- a/tests/Vue/Button/button.spec.js
+++ b/tests/Vue/Button/button.spec.js
@@ -4,7 +4,6 @@ import BaseButton from "../../../resources/js/components/base/BaseButton.vue";
import "@testing-library/jest-dom";
test("Verify that the button renders and that the default slot works", async () => {
- // The render method returns a collection of utilities to query your component.
const { getByText } = render(BaseButton, {
slots: { default: "Button test" },
});
diff --git a/tests/Vue/LoadingSpinner/loadingspinner.spec.js b/tests/Vue/LoadingSpinner/loadingspinner.spec.js
index e2098d5e..121185af 100644
--- a/tests/Vue/LoadingSpinner/loadingspinner.spec.js
+++ b/tests/Vue/LoadingSpinner/loadingspinner.spec.js
@@ -1 +1,11 @@
-// https://github.com/testing-library/vue-testing-library/tree/main/src/__tests__
+import { render } from "@testing-library/vue";
+import BaseLoadingSpinner from "../../../resources/js/components/base/BaseLoadingSpinner.vue";
+
+import "@testing-library/jest-dom";
+
+test("Verify that the loading message is rendered", async () => {
+ const { getByText } = render(BaseLoadingSpinner);
+
+ const loadingMessage = getByText("Loading products ...");
+ expect(loadingMessage).toBeInTheDocument();
+});
diff --git a/tests/Vue/Products/ShowallProducts.spec.js b/tests/Vue/Products/ShowallProducts.spec.js
new file mode 100644
index 00000000..a4bf43fa
--- /dev/null
+++ b/tests/Vue/Products/ShowallProducts.spec.js
@@ -0,0 +1,102 @@
+import { render } from "@testing-library/vue";
+import ShowAllProducts from "../../../resources/js/components/Products/ShowAllProducts.vue";
+import formatPrice from "../../../resources/js/utils/functions";
+
+import "@testing-library/jest-dom";
+
+jest.mock("swrv", () => ({
+ __esModule: true,
+ default: jest.fn(),
+}));
+
+const useRouterLink = {
+ template: "",
+};
+
+/**
+ * @description
+ * Test case to verify that products are rendered correctly with their respective details,
+ * and that the router-link component works as expected.
+ *
+ * The test will:
+ * 1. Mock the `useSWRV` hook to return a list of products.
+ * 2. Render the `ShowAllProducts` component.
+ * 3. Iterate through each product and check if the product's name, price, and image are present in the DOM.
+ * 4. Check if the list of images found by the role "img" matches the length of the products list.
+ *
+ * @async
+ * @function
+ * @name verifyProductsAreRenderedAndRouterLinkWorksTest
+ */
+test("Verify products are rendered and router-link works", async () => {
+ const useSWRV = require("swrv").default;
+ const products = [
+ {
+ id: 1,
+ name: "Product 1",
+ price: 100,
+ slug: "product-1",
+ imageUrl: "image1.jpg",
+ },
+ {
+ id: 2,
+ name: "Product 2",
+ price: 200,
+ slug: "product-2",
+ imageUrl: "image2.jpg",
+ },
+ ];
+ useSWRV.mockReturnValue({ data: products, error: null });
+
+ const { getByText, getAllByRole } = render(ShowAllProducts, {
+ global: {
+ components: {
+ "router-link": useRouterLink,
+ },
+ },
+ });
+
+ for (const product of products) {
+ expect(getByText(product.name)).toBeInTheDocument();
+ expect(getByText(formatPrice(product.price))).toBeInTheDocument();
+
+ const productLink = getByText(product.name).closest("a");
+ const productImage = productLink.querySelector("img");
+ expect(productImage).toHaveAttribute("src", product.imageUrl);
+ }
+
+ const images = getAllByRole("img");
+ expect(images.length).toBe(products.length);
+});
+
+/**
+ * @description
+ * Test case to verify that an error message is displayed when an error occurs while loading products.
+ *
+ * The test will:
+ * 1. Mock the `useSWRV` hook to return an error.
+ * 2. Render the `ShowAllProducts` component.
+ * 3. Check if the error message is present in the DOM.
+ *
+ * @async
+ * @function
+ * @name verifyErrorMessageIsDisplayedWhenErrorOccursTest
+ */
+test("Verify error message is displayed when error occurs", async () => {
+ const useSWRV = require("swrv").default;
+ useSWRV.mockReturnValue({
+ data: null,
+ error: new Error("Error loading products"),
+ });
+
+ const { getByText } = render(ShowAllProducts, {
+ global: {
+ components: {
+ "router-link": useRouterLink,
+ },
+ },
+ });
+
+ const errorMessage = getByText("Error loading products");
+ expect(errorMessage).toBeInTheDocument();
+});
diff --git a/tests/Vue/Products/SingleProduct.spec.js b/tests/Vue/Products/SingleProduct.spec.js
new file mode 100644
index 00000000..25608114
--- /dev/null
+++ b/tests/Vue/Products/SingleProduct.spec.js
@@ -0,0 +1,92 @@
+import { fireEvent, render } from "@testing-library/vue";
+
+import SingleProduct from "../../../resources/js/components/Products/SingleProduct.vue";
+import { useCart } from "../../../resources/js/store/useCart";
+import formatPrice from "../../../resources/js/utils/functions";
+
+import "@testing-library/jest-dom";
+
+jest.mock("swrv", () => ({
+ __esModule: true,
+ default: jest.fn(),
+}));
+
+// Mock the useCart function
+jest.mock("@/store/useCart", () => {
+ const addToCartMock = jest.fn();
+ return {
+ useCart: jest.fn(() => ({ addToCart: addToCartMock })),
+ __addToCartMock: addToCartMock,
+ };
+});
+
+// Mock the useRoute function
+jest.mock("vue-router", () => ({
+ useRoute: jest.fn(() => ({
+ params: {
+ slug: "product-1",
+ },
+ })),
+}));
+
+const useRouterLink = {
+ template: "",
+};
+
+/**
+ * Test case to verify the 'Add to Cart' functionality.
+ *
+ * @name Verify 'Add to Cart' functionality
+ * @function
+ * @async
+ * @memberof module:tests
+ *
+ * @description
+ * The test case does the following:
+ * 1. Mocks the `useSWRV` hook to return the product data.
+ * 2. Mocks the `useCart` store to use a Jest function as the `addToCart` method.
+ * 3. Renders the `SingleProduct` component with the necessary global components.
+ * 4. Verifies the product information is displayed.
+ * 5. Clicks the 'Add To Cart' button.
+ * 6. Verifies that the `addToCart` method in the store is called with the correct product.
+ *
+ * @returns {void}
+ */
+test("Verify 'Add to Cart' functionality", async () => {
+ const useSWRV = require("swrv").default;
+ const product = {
+ id: 1,
+ name: "Product 1",
+ price: 100,
+ slug: "product-1",
+ imageUrl: "image1.jpg",
+ description: "Sample description",
+ };
+ useSWRV.mockReturnValue({ data: product, error: null });
+
+ const addToCartMock = jest.fn();
+ useCart.mockReturnValue({
+ addToCart: addToCartMock,
+ });
+
+ const { getByText } = render(SingleProduct, {
+ global: {
+ components: {
+ "router-link": useRouterLink,
+ },
+ },
+ });
+
+ // Verify product information is displayed
+ expect(getByText(product.name)).toBeInTheDocument();
+ expect(getByText(formatPrice(product.price))).toBeInTheDocument();
+ expect(getByText(product.description)).toBeInTheDocument();
+
+ // Click the 'Add To Cart' button
+ const addToCartButton = getByText("Add To Cart");
+ await fireEvent.click(addToCartButton);
+
+ // Verify that the `addToCart` method in the store is called with the correct product
+ expect(addToCartMock).toHaveBeenCalledTimes(1);
+ expect(addToCartMock).toHaveBeenCalledWith({ item: product });
+});