diff --git a/.browserslistrc b/.browserslistrc
new file mode 100644
index 0000000..e94f814
--- /dev/null
+++ b/.browserslistrc
@@ -0,0 +1 @@
+defaults
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..5168571
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,10 @@
+# See https://git-scm.com/docs/gitattributes for more about git attribute files.
+
+# Mark the database schema as having been generated.
+db/schema.rb linguist-generated
+
+# Mark the yarn lockfile as having been generated.
+yarn.lock linguist-generated
+
+# Mark any vendored files as having been vendored.
+vendor/* linguist-vendored
diff --git a/.gitignore b/.gitignore
index 6eac730..7efd766 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,7 +34,6 @@
/public/packs
/public/packs-test
-/node_modules
/yarn-error.log
yarn-debug.log*
.yarn-integrity
@@ -43,3 +42,4 @@ yarn-debug.log*
Gemfile.lock
package-lock.json
yarn.lock
+node_modules/
diff --git a/app/__mocks__/axios.js b/app/__mocks__/axios.js
new file mode 100644
index 0000000..476666f
--- /dev/null
+++ b/app/__mocks__/axios.js
@@ -0,0 +1,6 @@
+export default {
+ get: jest.fn(),
+ post: jest.fn(),
+ put: jest.fn(),
+ delete: jest.fn()
+};
diff --git a/app/controllers/guitars_controller.rb b/app/controllers/guitars_controller.rb
new file mode 100644
index 0000000..7a0aab9
--- /dev/null
+++ b/app/controllers/guitars_controller.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+# rubocop:disable Metrics/MethodLength
+
+class GuitarsController < ApplicationController
+ def index
+ render json: [
+ {
+ name: 'guitar1',
+ url: 'https://rukminim1.flixcart.com/image/416/416/acoustic-guitar/x/8/w/topaz-blue-signature-original-imaefec7uhypjdr9.jpeg?q=70',
+ price: '100',
+ description: 'blablabla 1'
+ },
+ {
+ name: 'guitar2',
+ url: 'https://shop.brianmayguitars.co.uk/user/special/content/Antique%20Cherry%20a.jpg',
+ price: '200',
+ description: 'blablabla 2'
+ },
+ {
+ name: 'guitar3',
+ url: 'https://cdn.mos.cms.futurecdn.net/Yh6r74b8CAj2jbdf2FAhq4-970-80.jpg.webp',
+ price: '300',
+ description: 'blablabla 3'
+ }
+ ]
+ end
+end
+
+# rubocop:enable Metrics/MethodLength
diff --git a/app/javascript/components/GuitarGallery.js b/app/javascript/components/GuitarGallery.js
new file mode 100644
index 0000000..4a51896
--- /dev/null
+++ b/app/javascript/components/GuitarGallery.js
@@ -0,0 +1,52 @@
+import React, { useEffect, useState } from 'react';
+import axios from 'axios';
+import { Title, TitleSizes, Gallery, Spinner } from '@patternfly/react-core';
+import GuitarInfo from './GuitarInfo';
+import { guitarsUrl } from './constants';
+
+// Loading the data from the server:
+
+const GuitarGallery = () => {
+ const getGuitars = async () => {
+ try {
+ const response = await axios.get(guitarsUrl);
+ if (response.data) {
+ setGuitars(response.data);
+ }
+ } catch (error) {
+ console.error(error);
+ }
+ };
+
+ const [guitars, setGuitars] = useState([]);
+
+ useEffect(() => {
+ getGuitars();
+ }, []);
+
+ return (
+
+
+ Guitars For Sale:
+
+
+ {guitars.length === 0 ? (
+
+ ) : (
+ guitars.map(({ name, url, price, description }) => (
+
+ ))
+ )}
+
+
+ );
+};
+
+export default GuitarGallery;
diff --git a/app/javascript/components/GuitarInfo.js b/app/javascript/components/GuitarInfo.js
new file mode 100644
index 0000000..3706d64
--- /dev/null
+++ b/app/javascript/components/GuitarInfo.js
@@ -0,0 +1,38 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {
+ GalleryItem,
+ Grid,
+ GridItem,
+ Card,
+ CardTitle,
+ CardBody,
+ CardFooter
+} from '@patternfly/react-core';
+
+const GuitarInfo = ({ name, url, price, description }) => {
+ return (
+
+
+
+
+
+
+
+ {name}
+ Price: {price}
+ Description: {description}
+
+
+
+
+ );
+};
+GuitarInfo.propTypes = {
+ name: PropTypes.string.isRequired,
+ url: PropTypes.string.isRequired,
+ price: PropTypes.string.isRequired,
+ description: PropTypes.string.isRequired
+};
+
+export default GuitarInfo;
diff --git a/app/javascript/components/Users.js b/app/javascript/components/Users.js
deleted file mode 100644
index e267a4c..0000000
--- a/app/javascript/components/Users.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import axios from 'axios';
-import { Title, TitleSizes, List, ListItem } from '@patternfly/react-core';
-
-export const Users = () => {
- const [users, setUsers] = useState([]);
- useEffect(() => {
- const getUser = async () => {
- try {
- const response = await axios.get('/user');
- setUsers(response.data || []);
- } catch (error) {
- console.error(error);
- }
- };
- getUser();
- }, []);
-
- return (
-
-
- Users:
-
-
- {users.map((user, index) => (
- {user}
- ))}
-
-
- );
-};
diff --git a/app/javascript/components/constants.js b/app/javascript/components/constants.js
new file mode 100644
index 0000000..78c2ecb
--- /dev/null
+++ b/app/javascript/components/constants.js
@@ -0,0 +1 @@
+export const guitarsUrl = '/guitars';
diff --git a/app/javascript/components/tests/GuitarGallery.test.js b/app/javascript/components/tests/GuitarGallery.test.js
new file mode 100644
index 0000000..d06fb40
--- /dev/null
+++ b/app/javascript/components/tests/GuitarGallery.test.js
@@ -0,0 +1,33 @@
+import React from 'react';
+import { waitFor, render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom/extend-expect';
+import axios from 'axios';
+import GuitarGallery from '../GuitarGallery.js';
+import { guitarsUrl } from '../constants';
+import { guitarData } from './fixtures';
+
+test("show loader when it's fetching data, then render Guitars", async () => {
+ await axios.get.mockResolvedValueOnce(guitarData);
+ const { unmount, getByText } = render();
+
+ // check the correct url is called:
+ expect(axios.get).toHaveBeenCalledWith(guitarsUrl);
+ expect(axios.get).toHaveBeenCalledTimes(1);
+
+ // ensure the title of the page is presented in the page:
+ expect(screen.getByText('Guitars For Sale:')).toBeInTheDocument();
+
+ // Another option to test it using getByText that react-testing-library/render returns:
+ const title = getByText(/Guitars For Sale:/);
+ expect(title).toHaveTextContent('Guitars For Sale:');
+
+ // ensure some content of the page is presented in the page:
+
+ await waitFor(() => screen.getByText('Description: some description in guitar1_test'));
+
+ expect(screen.getByText('Description: some description in guitar1_test')).toBeInTheDocument();
+ expect(screen.getByText('Description: some description in guitar2_test')).toBeInTheDocument();
+
+ // unmnount the component from the DOM
+ unmount();
+});
diff --git a/app/javascript/components/tests/fixtures.js b/app/javascript/components/tests/fixtures.js
new file mode 100644
index 0000000..ab8ab8d
--- /dev/null
+++ b/app/javascript/components/tests/fixtures.js
@@ -0,0 +1,16 @@
+export const guitarData = {
+ data: [
+ {
+ name: 'guitar1_test',
+ url: 'https://rukminim1.flixcart.com/image/416/416/acoustic-guitar/x/8/w/topaz-blue-signature-original-imaefec7uhypjdr9.jpeg?q=70',
+ price: '100',
+ description: 'some description in guitar1_test'
+ },
+ {
+ name: 'guitar2_test',
+ url: 'https://shop.brianmayguitars.co.uk/user/special/content/Antique%20Cherry%20a.jpg',
+ price: '200',
+ description: 'some description in guitar2_test'
+ }
+ ]
+};
diff --git a/app/javascript/packs/hello_react.jsx b/app/javascript/packs/hello_react.jsx
index 35a0215..50ef0d4 100644
--- a/app/javascript/packs/hello_react.jsx
+++ b/app/javascript/packs/hello_react.jsx
@@ -3,9 +3,9 @@
// of the page.
import React from 'react';
import ReactDOM from 'react-dom';
-import { Users } from '../components/Users';
+import GuitarGallery from '../components/GuitarGallery';
import './fonts.css';
document.addEventListener('DOMContentLoaded', () => {
- ReactDOM.render(, document.body.appendChild(document.createElement('div')));
+ ReactDOM.render(, document.body.appendChild(document.createElement('div')));
});
diff --git a/app/views/home/index.html.erb b/app/views/home/index.html.erb
index 8b13789..e69de29 100644
--- a/app/views/home/index.html.erb
+++ b/app/views/home/index.html.erb
@@ -1 +0,0 @@
-
diff --git a/app/views/user/index.html.erb b/app/views/user/index.html.erb
deleted file mode 100644
index c825aaf..0000000
--- a/app/views/user/index.html.erb
+++ /dev/null
@@ -1,2 +0,0 @@
-User#index
-Find me in app/views/user/index.html.erb
diff --git a/config/routes.rb b/config/routes.rb
index 00a4a81..da964ae 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,8 +1,7 @@
# frozen_string_literal: true
Rails.application.routes.draw do
- root
- 'guitars#index'
-
+ root 'home#index'
+ get '/guitars', to: 'guitars#index'
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end
diff --git a/jest.config.js b/jest.config.js
index c7d3e00..0280af0 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -1,5 +1,199 @@
+/*
+ * For a detailed explanation regarding each configuration property, visit:
+ * https://jestjs.io/docs/configuration
+ */
// eslint-disable-next-line no-undef
module.exports = {
setupFilesAfterEnv: ['./rtl.setup.js'],
- testEnvironment: 'jsdom'
+
+ moduleNameMapper: {
+ '^.+\\.(css|scss)$': 'identity-obj-proxy'
+ },
+ // All imported modules in your tests should be mocked automatically
+ // automock: false,
+
+ // Stop running tests after `n` failures
+ // bail: 0,
+
+ // The directory where Jest should store its cached dependency information
+ // cacheDirectory: "/tmp/jest_rs",
+
+ // Automatically clear mock calls, instances, contexts and results before every test
+ clearMocks: true,
+
+ // Indicates whether the coverage information should be collected while executing the test
+ // collectCoverage: false,
+
+ // An array of glob patterns indicating a set of files for which coverage information should be collected
+ // collectCoverageFrom: undefined,
+
+ // The directory where Jest should output its coverage files
+ // coverageDirectory: undefined,
+
+ // An array of regexp pattern strings used to skip coverage collection
+ // coveragePathIgnorePatterns: [
+ // "/node_modules/"
+ // ],
+
+ // Indicates which provider should be used to instrument code for coverage
+ // coverageProvider: "babel",
+
+ // A list of reporter names that Jest uses when writing coverage reports
+ // coverageReporters: [
+ // "json",
+ // "text",
+ // "lcov",
+ // "clover"
+ // ],
+
+ // An object that configures minimum threshold enforcement for coverage results
+ // coverageThreshold: undefined,
+
+ // A path to a custom dependency extractor
+ // dependencyExtractor: undefined,
+
+ // Make calling deprecated APIs throw helpful error messages
+ // errorOnDeprecated: false,
+
+ // The default configuration for fake timers
+ // fakeTimers: {
+ // "enableGlobally": false
+ // },
+
+ // Force coverage collection from ignored files using an array of glob patterns
+ // forceCoverageMatch: [],
+
+ // A path to a module which exports an async function that is triggered once before all test suites
+ // globalSetup: undefined,
+
+ // A path to a module which exports an async function that is triggered once after all test suites
+ // globalTeardown: undefined,
+
+ // A set of global variables that need to be available in all test environments
+ // globals: {},
+
+ // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
+ // maxWorkers: "50%",
+
+ // An array of directory names to be searched recursively up from the requiring module's location
+ // moduleDirectories: [
+ // "node_modules"
+ // ],
+
+ // An array of file extensions your modules use
+ // moduleFileExtensions: [
+ // "js",
+ // "mjs",
+ // "cjs",
+ // "jsx",
+ // "ts",
+ // "tsx",
+ // "json",
+ // "node"
+ // ],
+
+ // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
+ // moduleNameMapper: {},
+
+ // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
+ // modulePathIgnorePatterns: [],
+
+ // Activates notifications for test results
+ // notify: false,
+
+ // An enum that specifies notification mode. Requires { notify: true }
+ // notifyMode: "failure-change",
+
+ // A preset that is used as a base for Jest's configuration
+ // preset: undefined,
+
+ // Run tests from one or more projects
+ // projects: undefined,
+
+ // Use this configuration option to add custom reporters to Jest
+ // reporters: undefined,
+
+ // Automatically reset mock state before every test
+ // resetMocks: false,
+
+ // Reset the module registry before running each individual test
+ // resetModules: false,
+
+ // A path to a custom resolver
+ // resolver: undefined,
+
+ // Automatically restore mock state and implementation before every test
+ // restoreMocks: false,
+
+ // The root directory that Jest should scan for tests and modules within
+ // rootDir: undefined,
+
+ // A list of paths to directories that Jest should use to search for files in
+ // roots: [
+ // ""
+ // ],
+
+ // Allows you to use a custom runner instead of Jest's default test runner
+ // runner: "jest-runner",
+
+ // The paths to modules that run some code to configure or set up the testing environment before each test
+ // setupFiles: [],
+
+ // A list of paths to modules that run some code to configure or set up the testing framework before each test
+ // setupFilesAfterEnv: [],
+
+ // The number of seconds after which a test is considered as slow and reported as such in the results.
+ // slowTestThreshold: 5,
+
+ // A list of paths to snapshot serializer modules Jest should use for snapshot testing
+ // snapshotSerializers: [],
+
+ // The test environment that will be used for testing
+ testEnvironment: 'jsdom',
+
+ // Options that will be passed to the testEnvironment
+ // testEnvironmentOptions: {},
+
+ // Adds a location field to test results
+ // testLocationInResults: false,
+
+ // The glob patterns Jest uses to detect test files
+ // testMatch: [
+ // "**/__tests__/**/*.[jt]s?(x)",
+ // "**/?(*.)+(spec|test).[tj]s?(x)"
+ // ],
+
+ // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
+ // testPathIgnorePatterns: [
+ // "/node_modules/"
+ // ],
+
+ // The regexp pattern or array of patterns that Jest uses to detect test files
+ // testRegex: [],
+
+ // This option allows the use of a custom results processor
+ // testResultsProcessor: undefined,
+
+ // This option allows use of a custom test runner
+ // testRunner: "jest-circus/runner",
+
+ // A map from regular expressions to paths to transformers
+ // transform: undefined,
+
+ // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
+ // transformIgnorePatterns: ['/node_modules/', '\\.pnp\\.[^\\/]+$']
+
+ transformIgnorePatterns: ['/node_modules/']
+
+ // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
+ // unmockedModulePathPatterns: undefined,
+
+ // Indicates whether each individual test should be reported during the run
+ // verbose: undefined,
+
+ // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
+ // watchPathIgnorePatterns: [],
+
+ // Whether to use watchman for file crawling
+ // watchman: true,
};
diff --git a/package.json b/package.json
index b5e0d73..d9d6e61 100644
--- a/package.json
+++ b/package.json
@@ -39,6 +39,7 @@
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.31.10",
"global-jsdom": "^8.6.0",
+ "identity-obj-proxy": "^3.0.0",
"jest": "^29.3.0",
"jest-environment-jsdom": "^29.3.1",
"jsdom": "^20.0.2",
diff --git a/test/controllers/guitars_controller_test.rb b/test/controllers/guitars_controller_test.rb
new file mode 100644
index 0000000..7810ff6
--- /dev/null
+++ b/test/controllers/guitars_controller_test.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+require 'test_helper'
+
+class GuitarsControllerTest < ActionDispatch::IntegrationTest
+ test 'get index' do
+ get guitars_index_url
+ assert_response :success
+ end
+end
diff --git a/test/controllers/user_controller_test.rb b/test/controllers/user_controller_test.rb
deleted file mode 100644
index 1c6182f..0000000
--- a/test/controllers/user_controller_test.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-require 'test_helper'
-
-class UserControllerTest < ActionDispatch::IntegrationTest
- test 'should get index' do
- get user_index_url
- assert_response :success
- end
-end