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 ( + + + + + Guitar Image + + + {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