Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Completed assessment #9

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion configs/postcss.config.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
module.exports = {
plugins: {
'postcss-preset-env': {
// Removing color-adjust from postcss as it does not recognize it as a valid feature, hence to recommendations to use color-mix, etc
features: {
'color-adjust': false
// 'color-adjust': false
}
},
'autoprefixer': {
overrideBrowserslist: ['last 2 versions', '> 1%'], // Ensure vendor prefixes for the last 2 versions of browsers
},
'cssnano': {
preset: ['default', {
discardUnused: false // Prevents optimizations that might include color-adjust
Expand Down
2 changes: 1 addition & 1 deletion configs/server.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const { uuid } = require('@keg-hub/jsutils')

module.exports = {
api: {
origins: [],
origins: ['http://localhost:3000'], // Add allowed origins to resolve cors error
port: 5005,
host: '0.0.0.0',
uuid: uuid(),
Expand Down
3 changes: 3 additions & 0 deletions container/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
NODE_ENV=development
PORT=3000
API_PORT=5005
21 changes: 21 additions & 0 deletions container/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Use the official Node.js 18 image as the base image
FROM node:18.17.1

# Set the working directory
WORKDIR /app

# Copy package.json and yarn.lock files
COPY package.json yarn.lock ./

# Install dependencies
RUN yarn install

# Copy the rest of the application code
COPY . .

# Expose the ports the app runs on
EXPOSE 3000
EXPOSE 5005

# Command to start the app
CMD ["yarn", "start"]
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"author": "Lance Tipton",
"license": "MIT",
"scripts": {
"doc:build": "docker build --file ./container/Dockerfile --tag tech:assessment --force-rm .",
"doc:build": "docker build -f ./container/Dockerfile -t tech:assessment .",
"doc:run": "docker run -it --rm --env-file ./container/.env -p 3000:3000 -p 5005:5005 tech:assessment",
"doc:test": "node scripts/docTest.js",
"api": "nodemon --watch ./server --watch ./configs --exec 'node ./server/index.js'",
Expand All @@ -15,6 +15,7 @@
"eslint": "eslint --config ./configs/eslintrc.config.js . --resolve-plugins-relative-to ./ --fix --quiet",
"pretty": "./node_modules/.bin/prettier --config ./configs/prettier.config.js --ignore-path .eslintignore --write '**/*.{js,jsx}'",
"start": "run-p web api",
"test": "jest",
"web": "webpack serve --config configs/webpack.js --progress --static src/"
},
"devDependencies": {
Expand Down Expand Up @@ -67,7 +68,7 @@
],
"dependencies": {
"@babel/plugin-transform-private-methods": "^7.24.1",
"autoprefixer": "10.4.5"
"autoprefixer": "^10.4.5"
},
"resolutions": {
"autoprefixer": "10.4.5"
Expand Down
4 changes: 3 additions & 1 deletion server/endpoints/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ const rootApi = require('./root')

// Register each router by priority, first matching route wins
module.exports = (app, config) => {
rootApi(app, config)
// Must register specific routes first
goatsApi(app, config)
// Catch all route should be registered last otherwise our fetch call will always return the uuid and value of inDocker
rootApi(app, config)
}
3 changes: 2 additions & 1 deletion server/libs/goatsLib.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ const { doIt } = require('@keg-hub/jsutils')
const factList = require('./goatFacts.json')

const getRandomFact = () => {
throw new Error(`Random goat fact code not implemented!`)
const randomIndex = Math.floor(Math.random() * factList.length) // grab a random fact from the imported array
return factList[randomIndex]
}

const goatFacts = async () => {
Expand Down
14 changes: 13 additions & 1 deletion src/addGoatFacts.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,17 @@
* addGoatFacts - Adds the passed in goat facts to the Dom
*/
export const addGoatFacts = (facts = []) => {
console.error(`Step 5. Add Goat Facts to the DOM!`)
if (facts.length === 0) {
console.error(`The fact is there are no facts!`)
return
}
const list = document.getElementById('goat-facts-list')
// Remove any child nodes
list.innerHTML = ''
// Create new element and append to the parent container
facts.forEach(fact => {
let listItem = document.createElement('li')
listItem.textContent = fact
list.appendChild(listItem)
})
}
38 changes: 38 additions & 0 deletions src/addGoatFacts.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const { addGoatFacts } = require('./addGoatFacts')

describe('addGoatFacts', () => {
let list

beforeEach(() => {
// Set up our document body
document.body.innerHTML = '<ul id="goat-facts-list"></ul>'
list = document.getElementById('goat-facts-list')
})

it('should add goat facts to the DOM', () => {
const facts = ['Goats have rectangular pupils.', 'Goats have a four-chamber stomach.']
addGoatFacts(facts)
expect(list.children.length).toBe(2)
expect(list.children[0].textContent).toBe('Goats have rectangular pupils.')
expect(list.children[1].textContent).toBe('Goats have a four-chamber stomach.')
});

it('should handle an empty array', () => {
console.error = jest.fn()
addGoatFacts([])
expect(list.children.length).toBe(0)
expect(console.error).toHaveBeenCalledWith('The fact is there are no facts!')
})

it('should clear previous facts before adding new ones', () => {
const initialFacts = ['Initial fact']
addGoatFacts(initialFacts)
expect(list.children.length).toBe(1)

const newFacts = ['New fact 1', 'New fact 2']
addGoatFacts(newFacts)
expect(list.children.length).toBe(2)
expect(list.children[0].textContent).toBe('New fact 1')
expect(list.children[1].textContent).toBe('New fact 2')
});
});
10 changes: 6 additions & 4 deletions src/filterGoatFacts.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/**
* filterGoatFacts - Filters goat facts based on word and index
*/
export const filterGoatFacts = facts => {
console.error(`Step 6. Goat Facts filters!`)

return facts
export const filterGoatFacts = (currentFacts, textValue, indexValue) => {
const filteredFacts = currentFacts.filter(fact => {
const words = fact.split(' ')
return words[indexValue] === textValue
})
return filteredFacts
}
28 changes: 28 additions & 0 deletions src/filterGoatFacts.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const { filterGoatFacts } = require('./filterGoatFacts')

describe('filterGoatFacts', () => {
const currentFacts = [
'Goats have rectangular pupils',
'Goats have a four-chamber stomach',
'Goats were one of the first animals to be domesticated',
'There are over 300 distinct breeds of goats'
]

it('should filter facts based on the word and index', () => {
const filteredFacts = filterGoatFacts(currentFacts, 'Goats', 0)
expect(filteredFacts.length).toBe(3)
expect(filteredFacts).toContain('Goats have rectangular pupils')
expect(filteredFacts).toContain('Goats have a four-chamber stomach')
expect(filteredFacts).toContain('Goats were one of the first animals to be domesticated')
})

it('should return an empty array if no facts match', () => {
const filteredFacts = filterGoatFacts(currentFacts, 'nonexistent', 0)
expect(filteredFacts.length).toBe(0)
})

it('should handle indices that are out of bounds', () => {
const filteredFacts = filterGoatFacts(currentFacts, 'Goats', 10)
expect(filteredFacts.length).toBe(0)
})
})
12 changes: 11 additions & 1 deletion src/getGoatFacts.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
/**
* getGoatFacts - Gets a list of goat facts from the backend API
*/
const { addGoatFacts } = require('./addGoatFacts') // import the addGoatFacts function to update the DOM if facts are present in our response
export const getGoatFacts = async () => {
console.error(`Step 4. Goat Facts api call!`)
try {
const response = await fetch('http://localhost:5005/goats');
if (!response.ok) {
throw new Error('Network response was not ok ' + response.statusText);
}
const data = await response.json();
if (data.data.length > 0) return data.data
} catch (error) {
console.error('There has been a problem with your fetch operation:', error);
}
}
56 changes: 56 additions & 0 deletions src/getGoatFacts.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { getGoatFacts } from './getGoatFacts'

global.fetch = jest.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ data: ['Fact 1', 'Fact 2', 'Fact 3'] }),
})
)

describe('getGoatFacts', () => {
beforeEach(() => {
fetch.mockClear()
})

it('should fetch goat facts from the API', async () => {
const facts = await getGoatFacts()
expect(facts).toEqual(['Fact 1', 'Fact 2', 'Fact 3'])
expect(fetch).toHaveBeenCalledTimes(1)
expect(fetch).toHaveBeenCalledWith('http://localhost:5005/goats')
})

it('should handle an error response', async () => {
fetch.mockImplementationOnce(() =>
Promise.resolve({
ok: false,
statusText: 'Internal Server Error'
})
)

const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {})

const facts = await getGoatFacts()
expect(facts).toBeUndefined()
expect(consoleSpy).toHaveBeenCalledWith(
'There has been a problem with your fetch operation:',
new Error('Network response was not ok Internal Server Error')
)

consoleSpy.mockRestore()
})

it('should handle a fetch exception', async () => {
fetch.mockImplementationOnce(() => Promise.reject(new Error('Fetch failed')))

const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {})

const facts = await getGoatFacts()
expect(facts).toBeUndefined()
expect(consoleSpy).toHaveBeenCalledWith(
'There has been a problem with your fetch operation:',
new Error('Fetch failed')
)

consoleSpy.mockRestore()
})
})
10 changes: 10 additions & 0 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ <h5 class="card-title">
>
Get Goat Facts
</button>
<div class="input-container">
<div class="input-element">
<label for="text-content">Text Content: Must be a word w/out spaces</label>
<input type="text" id="text-content" name="text-content">
</div>
<div class="input-element">
<label for="index">Index: Must be a number</label>
<input type="number" id="index" name="index">
</div>
</div>
</div>
</div>

Expand Down
19 changes: 10 additions & 9 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,22 @@ import './styles/bootstrap.min.css'
import { getGoatFacts } from './getGoatFacts'
import { addGoatFacts } from './addGoatFacts'
import { filterGoatFacts } from './filterGoatFacts'
import { validateAndUpdate } from './validateInput'

/**
* onGetGoatFacts - Action to update the goat facts displayed on the Dom
*/
const getGoatFactsBtn = document.getElementById('get-goat-facts');

const onGetGoatFacts = async () => {
console.error(`Step 3. Should be called by the Get Goat Facts button!`)
const rootContainer = document.querySelector('.card');

const facts = await getGoatFacts()

const filteredFacts = filterGoatFacts(facts)

addGoatFacts(filteredFacts)
addGoatFacts(facts)
rootContainer.classList.add('loaded')
validateAndUpdate(facts)
}

;(async () => {
console.error(`Step 2. Open the browser inspector!`)
await onGetGoatFacts()
})()
getGoatFactsBtn.addEventListener('click', onGetGoatFacts);

// Removed the IIAFE in order to setup the button to call the function
2 changes: 1 addition & 1 deletion src/styles/bootstrap.min.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/styles/bootstrap.min.css.map

Large diffs are not rendered by default.

47 changes: 47 additions & 0 deletions src/styles/style.css
Original file line number Diff line number Diff line change
@@ -1,2 +1,49 @@
/* Site CSS */
#goat-facts-list {
list-style-type: none;
padding: 0;
}

#goat-facts-list li {
padding: 10px;
margin: 6px 0;
color: black;
border-radius: 0.25rem;
}

#goat-facts-list li:nth-child(odd) {
color: white;
background-color: #0d6efd;
}

.card {
margin-bottom: 50px;
}

.card:not(.loaded) .card-footer .input-container {
display: none;
}

.card .card-footer,
.card .card-footer .input-container {
display: flex;
align-items: center;
}

.card .card-footer .input-element {
margin-left: 25px;
}

@media (max-width: 641px) {
.card .card-footer,
.card .card-footer .input-container {
flex-direction: column;
}

.card .card-footer .input-element {
display: flex;
flex-direction: column;
margin: 25px 0 0 0;
}
}

Loading