diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 68e7f1a..0000000 --- a/.babelrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "presets": ["react", "env"] -} \ No newline at end of file diff --git a/.circleci/config.yml b/.circleci/config.yml index e614d1a..4a90615 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -20,6 +20,10 @@ jobs: name: Install Project Dependencies command: yarn install --frozen-lockfile + - run: + name: Run vacuum cleaner + command: yarn clean + - run: name: Run Linter command: yarn lint diff --git a/.eslintrc b/.eslintrc index 90f53c1..db6c521 100644 --- a/.eslintrc +++ b/.eslintrc @@ -7,14 +7,10 @@ }, "sourceType": "module" }, - "extends": [ - "plugin:react/recommended" - ], "env": { "browser": true, "node": true }, - "plugins": ["react"], "rules": { "comma-dangle": [2,"never"], "no-cond-assign": 2, diff --git a/.gitattributes b/.gitattributes index f2283e6..8f7ef3f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,4 +5,5 @@ *.jpg binary *.png binary -*.gif binary \ No newline at end of file +*.gif binary +*.ico binary \ No newline at end of file diff --git a/.gitignore b/.gitignore index 297491f..7653916 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ .cache/ public/ node_modules/ -reports/ \ No newline at end of file +reports/ +bak/ +.greenwood/ \ No newline at end of file diff --git a/README.md b/README.md index e4d1c16..83bbe18 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,14 @@ # www.thegreenhouse.io -Personal blog / website at [https://www.thegreenhouse.io](https://www.thegreenhouse.io) +[![CircleCI](https://circleci.com/gh/thegreenhouseio/www.thegreenhouse.io/tree/master.svg?style=svg)](https://circleci.com/gh/thegreenhouseio/www.thegreenhouse.io/tree/master) +[![Netlify Status](https://api.netlify.com/api/v1/badges/2c9b7dc5-c01a-43eb-86a4-094f7720e2fd/deploy-status)](https://app.netlify.com/sites/silly-snyder-ece0b2/deploys) +[![GitHub release](https://img.shields.io/github/tag/ProjectEvergreen/greenwood.svg)](https://github.com/ProjectEvergreen/greenwood/tags) +[![GitHub issues](https://img.shields.io/github/issues-raw/ProjectEvergreen/greenwood.svg)](https://github.com/ProjectEvergreen/greenwood/issues) +[![GitHub issues](https://img.shields.io/github/issues-pr-raw/ProjectEvergreen/greenwood.svg)](https://github.com/ProjectEvergreen/greenwood/issues) +[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/ProjectEvergreen/greenwood/master/LICENSE.md) + ## Overview -This is a static site for The Greenhouse's website built with [Gatsby](https://www.gatsbyjs.org/). Has the usual stuff, e.g. Blog posts, about me, contact form, etc. 🌟 💯 +This is a static site for [The Greenhouse's website](https://www.thegreenhouse.io) built with [Greenwood](https://www.greenwoodjs.io/). Has the usual stuff, e.g. Blog posts, about me, contact form, etc. 🌟 💯 ## Development Stuff to do / run. 🏃‍♂️ @@ -12,25 +18,14 @@ After cloning the repo, do the following to get up and running 1. Install [NodeJS](https://nodejs.org/en/) (LTS) 1. Install [Yarn](https://yarnpkg.com/en/) (>= 1.x) 1. Run `yarn install` +1. Add `127.0.0.1 local.thegreenhouse.io` to your `/etc/hosts` file (optional) ### Tasks -- `yarn develop` - start local development server with file watching, live reload, etc -- `yarn build` - build the site for production deployment -- `yarn serve` - builds the site for production and starts a server locally (useful for a local demo) -- `yarn release --release_env=[stage|prod]` - deploys a build to S3, to either stage or prod -- `yarn test --[watch|coverage]` - run tests, opttionally in watch mode and / or to generate code coverage +- `yarn develop` - Start a local development server with file watching, live reload, etc +- `yarn build` - Build the site for production deployment +- `yarn serve` - Build the site for production and start a server locally (useful for a local demo) ## Release Management -The project is hosted in [AWS](https://aws.amazon.com/) and is setup to deploy continously on every merge to master in GitHub by running _running.js_. -1. Jenkins build and deploys to an S3 bucket -1. CloudFront fronts this S3 bucket -1. Route53 has a `CNAME` entry mapping _www.thegreenhouse.io_ to the CloudFront distrubution - -To release manually run `yarn build && yarn release --release_env=[stage|prod]` - -**Note:** the release expects the following access credentials to be available, either as environment variables or in a file, e.g _~/.aws/credentials.json_. -- `AWS_ACCESS_KEY_ID` -- `AWS_SECRET_ACCESS_KEY` - +The project is hosted by [Netlify](https://www.netlify.com/) and is setup to deploy continously on every merge to master in GitHub. -**Note: see _release.js_ for additional variables needed.** \ No newline at end of file +CircleCI is used for continuous integration on PRs and Netlify will deploy preview builds for all PRs. \ No newline at end of file diff --git a/gatsby-config.js b/gatsby-config.js deleted file mode 100644 index 2c20752..0000000 --- a/gatsby-config.js +++ /dev/null @@ -1,36 +0,0 @@ -module.exports = { - siteMetadata: { - title: 'The Greenhouse I/O', - siteUrl: 'https://www.thegreenhouse.io', - description: 'Personal / portfolio website for The Greenhouse.' - }, - plugins: [ - 'gatsby-plugin-typography', - 'gatsby-plugin-sitemap', - 'gatsby-plugin-react-helmet', - { - resolve: 'gatsby-plugin-google-analytics', - options: { - trackingId: 'UA-117350131-1', - head: true - } - }, { - resolve: 'gatsby-plugin-favicon', - options: { - logo: './src/favicon.jpg', - injectHTML: true, - icons: { - android: true, - appleIcon: true, - appleStartup: true, - coast: false, - favicons: true, - firefox: true, - twitter: false, - yandex: false, - windows: false - } - } - } - ] -}; \ No newline at end of file diff --git a/greenwood.config.js b/greenwood.config.js new file mode 100644 index 0000000..fc4f1b9 --- /dev/null +++ b/greenwood.config.js @@ -0,0 +1,26 @@ +const DESCRIPTION = 'Personal site and blog for Owen Buckley and The Greenhouse I/O. Ideas are built here.'; +const FAVICON_HREF = '/assets/favicon.ico'; +const TITLE = 'The Greenhouse I/O'; + +module.exports = { + title: TITLE, + + meta: [ + { name: 'description', content: DESCRIPTION }, + { name: 'twitter:creator', content: '@thegreenhouseio' }, + { name: 'twitter:site', content: '@thegreenhouseio' }, + { property: 'og:title', content: TITLE }, + { property: 'og:type', content: 'website' }, + { property: 'og:url', content: 'https://www.greenwoodjs.io' }, + { property: 'og:image', content: 'https://s3.amazonaws.com/www.thegreenhouse.io/static/banner.4b3f4ebd.jpg' }, + { property: 'og:description', content: DESCRIPTION }, + { rel: 'shortcut icon', href: FAVICON_HREF }, + { rel: 'icon', href: FAVICON_HREF } + ], + + devServer: { + port: 1981, + host: 'local.thegreenhouse.io' + } + +}; \ No newline at end of file diff --git a/jest.config.json b/jest.config.json deleted file mode 100644 index 4b24bde..0000000 --- a/jest.config.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "verbose": true, - - "setupFiles": [ - "raf/polyfill" - ], - - "coverageReporters": ["html", "json", "lcov", "cobertura"], - - "moduleNameMapper": { - "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/test/__mocks__/empty-module.js", - "\\.(css|scss)$": "/test/__mocks__/empty-module.js" - }, - - "coverageDirectory": "/reports/test-coverage", - - "collectCoverageFrom": [ - "src/components/**/*.{js,jsx}", - "src/layouts/*.{js,jsx}", - "src/pages/*.{js,jsx}", - "src/pages/blog/*.{js,jsx}", - "src/services/**/*.{js,jsx}", - "!src/**/**/*.spec.*" - ], - - "testPathIgnorePatterns": [ - "[/\\\\](\\.cache|node_modules)[/\\\\]" - ], - - "coverageThreshold": { - "global": { - "branches": 75, - "functions": 80, - "lines": 70, - "statements": 70 - } - } -} \ No newline at end of file diff --git a/package.json b/package.json index f22807c..38bac54 100644 --- a/package.json +++ b/package.json @@ -2,44 +2,20 @@ "name": "www.thegreenhouse.io", "version": "1.8.0", "description": "Personal / portfolio website for The Greenhouse.", - "main": "src/pages/index.jsx", - "repository": "git@github.com:thegreenhouseio/www.thegreenhouse.io.git", + "repository": "https://github.com/thegreenhouseio/www.thegreenhouse.io", "author": "owen@thegreenhouse.io", "license": "MIT", "scripts": { "clean": "rimraf ./reports && rimraf ./public", - "develop": "gatsby develop", - "build": "yarn lint && yarn clean && gatsby build", - "lint": "eslint *.js src/**/**/*.js* test/**/**/*.jsx", - "serve": "yarn build && gatsby serve", - "test": "yarn clean && jest --config ./jest.config.json", - "release": "node release.js" - }, - "dependencies": { - "gatsby-plugin-google-analytics": "^1.0.28", - "prop-types": "^15.6.1", - "react-helmet": "^5.2.0", - "react-social-icons": "^3.0.0", - "typography": "^0.16.6" + "develop": "greenwood develop", + "build": "yarn clean && greenwood build", + "lint": "eslint \"*.js\" \"src/**/**/*.js\"", + "serve": "yarn build && ws -d ./public", + "start": "yarn develop", + "test": "echo OOPS!!!!!" }, "devDependencies": { - "aws-sdk": "^2.211.0", - "babel-preset-react": "^6.24.1", - "enzyme": "^3.3.0", - "enzyme-adapter-react-15": "^1.0.5", - "eslint": "^4.19.1", - "eslint-plugin-react": "^7.7.0", - "gatsby": "^1.9.236", - "gatsby-cli": "^1.1.46", - "gatsby-link": "^1.6.39", - "gatsby-plugin-favicon": "^2.1.1", - "gatsby-plugin-react-helmet": "^2.0.11", - "gatsby-plugin-sitemap": "^1.2.23", - "gatsby-plugin-typography": "^1.7.18", - "glob": "^7.1.2", - "jest": "^22.4.3", - "react-test-renderer": "^16.2.0", - "rimraf": "^2.6.2", - "yargs": "^11.0.0" + "@greenwood/cli": "^0.3.5", + "eslint": "^6.1.0" } -} +} \ No newline at end of file diff --git a/release.js b/release.js deleted file mode 100644 index 2f73f2c..0000000 --- a/release.js +++ /dev/null @@ -1,162 +0,0 @@ - -/* - * - * This script assumes the following configuration is available in addition to any access credentials - * - process.env.release_env - used determines the release environment, values can be prod or stage, with stage the default - * - AWS_CLOUDFRONT_DISTRIBUTION_ID_PROD - * - AWS_CLOUDFRONT_DISTRIBUTION_ID_STAGE - * - */ - -const AWS = require('aws-sdk'); -const fs = require('fs'); -const glob = require('glob'); -const yargs = require('yargs').argv; - -const s3 = new AWS.S3(); // eslint-disable-line no-unused-vars -const cloudfront = new AWS.CloudFront(); - -// AWS CONFIGURATIONS -const AWS_REGION = 'us-east-1'; -const AWS_S3_BUCKET = { - PROD: 'www.thegreenhouse.io', - STAGE: 'stage.thegreenhouse.io' -}; - -const AWS_CLOUDFRONT_DISTRIBUTION = { - PROD: process.env.AWS_CLOUDFRONT_DISTRIBUTION_ID_PROD, - STAGE: process.env.AWS_CLOUDFRONT_DISTRIBUTION_ID_STAGE, - INVALIDATION_KEY: 'index.html', - INVALIDATION_PATHS: ['/*'] -}; - -// used to determine whether to deploy to prod or stage -// PROD is set using a manual release process, so STAGE is default -const RELEASE_ENVIRONMENT = yargs.release_env === 'prod' ? 'PROD' : 'STAGE'; - -console.log(`Releasing to => ${RELEASE_ENVIRONMENT }`); // eslint-disable-line no-console - -AWS.config.region = AWS_REGION; - -// helpful for simple debugging -// s3.listBuckets(function(err, data) { -// if (err) { -// console.log("Error:", err); -// } else { -// for (var index in data.Buckets) { -// let bucket = data.Buckets[index]; -// console.log("Bucket: ", bucket.Name, ' : ', bucket.CreationDate); -// } -// } -// }); - -// uploads the build directory to S3, our "main method" -glob('./public/**/**', function (er, files) { - for (let i = 0, l = files.length; i < l; i += 1) { - const filename = files[i]; - const s3Filename = filename.replace('./public/', ''); - - // upload only files - if (s3Filename.indexOf('.') > 0) { - const extension = filename.slice(filename.lastIndexOf('.')); - const contentType = getContentType(extension); - const body = fs.readFileSync(filename); // .pipe(zlib.createGzip()); - - const s3 = new AWS.S3({ - params: { - Bucket: AWS_S3_BUCKET[RELEASE_ENVIRONMENT], - Key: s3Filename, - ContentType: contentType, - ACL: 'public-read' - } - }); - - s3.upload({ Body: body }).on('httpUploadProgress', httpUploadProgress).send(httpUploadSend); - } - } -}); - -// mainly here so there's something fun to see in the jenkins build -function httpUploadProgress(evt) { - console.log(evt); // eslint-disable-line no-console -} - -// watches for index.html upload and triggers a cache bust in cloudfront -function httpUploadSend(err, data) { - console.log(err, data); // eslint-disable-line no-console - // trigger an invalidation to cache bust the site on each release - if (!err && data.key === AWS_CLOUDFRONT_DISTRIBUTION.INVALIDATION_KEY) { - invalidateCloudfrontDistribution(); - } -} - -// creates an invalidatation in cloudfront for /index.html for cache busting on each release -function invalidateCloudfrontDistribution() { - const timestamp = new Date().getTime(); - const paths = AWS_CLOUDFRONT_DISTRIBUTION.INVALIDATION_PATHS; - - const params = { - DistributionId: AWS_CLOUDFRONT_DISTRIBUTION[RELEASE_ENVIRONMENT], - InvalidationBatch: { - CallerReference: `jenkins-release-${RELEASE_ENVIRONMENT}-${timestamp}`, - Paths: { - Quantity: paths.length, - Items: paths - } - } - }; - - cloudfront.createInvalidation(params, function(err, data) { - const invalidationObjectKey = AWS_CLOUDFRONT_DISTRIBUTION.INVALIDATION_KEY; - - if (err) { - console.log(`FAILED: on ${invalidationObjectKey} invalidation request`); // eslint-disable-line no-console - console.log(err, err.stack); // eslint-disable-line no-console - console.log(data); // eslint-disable-line no-console - } else { - console.log(`SUCCESS: for ${invalidationObjectKey} invalidation request`); // eslint-disable-line no-console - } - }); -} - -// appropriately set objects content-type when uploading build to S3 -function getContentType(extension) { - let contentType = ''; - - switch (extension) { - - case '.eot': - contentType = 'application/vnd.ms-fontobject'; - break; - case '.jpg': - contentType = 'image/jpeg'; - break; - case '.js': - contentType = 'application/javascript'; - break; - case '.otf': - contentType = 'application/x-font-opentype'; - break; - case '.png': - contentType = 'image/png'; - break; - case '.svg': - contentType = 'image/svg+xml'; - break; - case '.ttf': - contentType = 'application/x-font-ttf'; - break; - case '.woff': - contentType = 'application/font-woff'; - break; - case '.woff2': - contentType = 'application/font-woff2'; - break; - default: - contentType = 'text/' + extension.replace('.', ''); - break; - - } - - return contentType; -} \ No newline at end of file diff --git a/src/components/header/banner.jpg b/src/assets/banner.jpg similarity index 100% rename from src/components/header/banner.jpg rename to src/assets/banner.jpg diff --git a/src/pages/blog/images/angular-shield.jpg b/src/assets/blog-post-images/angular-shield.jpg similarity index 100% rename from src/pages/blog/images/angular-shield.jpg rename to src/assets/blog-post-images/angular-shield.jpg diff --git a/src/pages/blog/images/docker.png b/src/assets/blog-post-images/docker.png similarity index 100% rename from src/pages/blog/images/docker.png rename to src/assets/blog-post-images/docker.png diff --git a/src/pages/blog/images/github.png b/src/assets/blog-post-images/github.png similarity index 100% rename from src/pages/blog/images/github.png rename to src/assets/blog-post-images/github.png diff --git a/src/pages/blog/images/minecraft-cloud.png b/src/assets/blog-post-images/minecraft-cloud.png similarity index 100% rename from src/pages/blog/images/minecraft-cloud.png rename to src/assets/blog-post-images/minecraft-cloud.png diff --git a/src/pages/blog/images/php.png b/src/assets/blog-post-images/php.png similarity index 100% rename from src/pages/blog/images/php.png rename to src/assets/blog-post-images/php.png diff --git a/src/pages/blog/images/pwa-lighthouse.png b/src/assets/blog-post-images/pwa-lighthouse.png similarity index 100% rename from src/pages/blog/images/pwa-lighthouse.png rename to src/assets/blog-post-images/pwa-lighthouse.png diff --git a/src/pages/blog/images/vue.png b/src/assets/blog-post-images/vue.png similarity index 100% rename from src/pages/blog/images/vue.png rename to src/assets/blog-post-images/vue.png diff --git a/src/assets/favicon.ico b/src/assets/favicon.ico new file mode 100644 index 0000000..6778b49 Binary files /dev/null and b/src/assets/favicon.ico differ diff --git a/src/assets/logos/.DS_Store b/src/assets/logos/.DS_Store new file mode 100644 index 0000000..03aa8e6 Binary files /dev/null and b/src/assets/logos/.DS_Store differ diff --git a/src/assets/logos/default.svg b/src/assets/logos/default.svg new file mode 100755 index 0000000..0719d78 --- /dev/null +++ b/src/assets/logos/default.svg @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/src/assets/logos/github.svg b/src/assets/logos/github.svg new file mode 100755 index 0000000..bf0cf26 --- /dev/null +++ b/src/assets/logos/github.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/assets/logos/linkedin.svg b/src/assets/logos/linkedin.svg new file mode 100644 index 0000000..6dcd55d --- /dev/null +++ b/src/assets/logos/linkedin.svg @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/src/assets/logos/medium.svg b/src/assets/logos/medium.svg new file mode 100644 index 0000000..bfaaadc --- /dev/null +++ b/src/assets/logos/medium.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/logos/meetup.svg b/src/assets/logos/meetup.svg new file mode 100644 index 0000000..ab1bd9f --- /dev/null +++ b/src/assets/logos/meetup.svg @@ -0,0 +1,49 @@ + + + + + + diff --git a/src/assets/logos/twitter.svg b/src/assets/logos/twitter.svg new file mode 100755 index 0000000..6b421ee --- /dev/null +++ b/src/assets/logos/twitter.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + diff --git a/src/components/blog-post/blog-post.css b/src/components/blog-post/blog-post.css index b1462a1..7d2e0ed 100644 --- a/src/components/blog-post/blog-post.css +++ b/src/components/blog-post/blog-post.css @@ -1,9 +1,3 @@ -.blog-post p { - width: 60%; - margin: 10px auto; - padding: 10px; -} - .blog-post .header { background-repeat: no-repeat; background-position: center; @@ -12,8 +6,13 @@ } .blog-post .header h1, .blog-post .header h5 { - width: 100%; - display: inline-block; + text-align: center; +} + +/* .blog-post p { + width: 60%; + margin: 10px auto; + padding: 10px; } .blog-post ul { @@ -30,4 +29,4 @@ .blog-post img { display: inline-block; width: 70%; -} \ No newline at end of file +} */ \ No newline at end of file diff --git a/src/components/blog-post/blog-post.js b/src/components/blog-post/blog-post.js new file mode 100644 index 0000000..1227b5e --- /dev/null +++ b/src/components/blog-post/blog-post.js @@ -0,0 +1,67 @@ + +import { html, LitElement } from 'lit-element'; + +import blogPostCss from './blog-post.css'; + +export function slugifyDate(date) { + const dateArray = date.split('.'); + + return `${dateArray[2]}/${dateArray[1]}/${dateArray[0]}/`; +} + +/* + * + * Notes for social sharing: + * - data intentionally set to reference production + * - `props.date` needs to match the folder path of the blog post + * + */ +class BlogPostComponent extends LitElement { + + constructor() { + super(); + } + + static get properties() { + return { + date: { + type: String + }, + description: { + type: String + }, + image: { + type: String + }, + title: { + type: String + } + }; + } + + render() { + const { date, image, title } = this; + const headerBackgroundStyle = `background-image: url("${image}")`; + + return html` + + +
+ +
+

${title}

+
Published: ${date}
+
+ +
+ +
+ +
+ `; + } +} + +customElements.define('app-blog-post', BlogPostComponent); \ No newline at end of file diff --git a/src/components/blog-post/blog-post.jsx b/src/components/blog-post/blog-post.jsx deleted file mode 100644 index 81852dd..0000000 --- a/src/components/blog-post/blog-post.jsx +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react'; -import Helmet from 'react-helmet'; -import PropTypes from 'prop-types'; -import './blog-post.css'; - -export function slugifyDate(date) { - const dateArray = date.split('.'); - - return `${dateArray[2]}/${dateArray[1]}/${dateArray[0]}/`; -} - -/* - * - * Notes for social sharing: - * - data intentionally set to reference production - * - `props.date` needs to match the folder path of the blog post - * - */ -const BlogPost = (props) => { - const isRemoteUrl = props.image.includes('http'); - const canonicalUrl = `https://www.thegreenhouse.io/blog/${slugifyDate(props.date)}`; - const canonicalImageUrl = isRemoteUrl ? props.image : `https://s3.amazonaws.com/www.thegreenhouse.io${props.image}`; - const description = props.description ? props.description : props.title; - const headerBackgroundStyle = { - backgroundImage: `url('${props.image}')` - }; - - return ( -
- - - - - - - - -
- -
-

{props.title}

-
Published: {props.date}
-
- -
- { props.children } -
- -
-
- ); -}; - -BlogPost.propTypes = { - title: PropTypes.string.isRequired, - description: PropTypes.string, - date: PropTypes.string.isRequired, - children: PropTypes.object.isRequired, - image: PropTypes.string.isRequired -}; - -export default BlogPost; \ No newline at end of file diff --git a/src/components/blog-post/blog-post.spec.jsx b/src/components/blog-post/blog-post.spec.jsx deleted file mode 100644 index b9015a6..0000000 --- a/src/components/blog-post/blog-post.spec.jsx +++ /dev/null @@ -1,170 +0,0 @@ -import * as React from 'react'; -import { mount, configure } from 'enzyme'; -import Adapter from 'enzyme-adapter-react-15'; -import Helmet from 'react-helmet'; -import { slugifyDate } from './blog-post'; -import BlogPost from './blog-post'; - -configure({ adapter: new Adapter() }); - -describe('BlogPost Component', () => { - - describe('slugifyDate', () => { - const mockPost = { - title: 'Some title for this post', - date: '04.11.2018', - image: 'image.png' - }; - - it('should return a slugified date when the right format is provided', () => { - const slugifiedDate = slugifyDate(mockPost.date); - - expect(slugifiedDate).toBe('2018/11/04/'); - }); - }); - - describe('basic functionality', () => { - const mockPost = { - title: 'Some title for this post', - date: '04.11.2018', - image: 'image.png' - }; - let post; - - beforeEach(() => { - post = mount( - -

Hello World

-
- ); - }); - - it('should not be null', () => { - expect(post).not.toBeNull(); - expect(post.find('.blog-post').length).toEqual(1); - }); - - it('should display the correct title', () => { - const title = post.find('h1.title'); - - expect(title.length).toBe(1); - expect(title.text()).toBe(mockPost.title); - }); - - it('should display the correct date', () => { - const date = post.find('h5.date'); - - expect(date.length).toBe(1); - expect(date.text()).toBe(`Published: ${mockPost.date}`); - }); - - it('should display the correct image if the image path is local', () => { - const header = post.find('.header'); - - expect(header.length).toBe(1); - expect(header.props().style.backgroundImage).toBe(`url(\'${mockPost.image}\')`); - }); - - it('should display the correct content', () => { - const content = post.find('.content'); - - expect(content.length).toBe(1); - expect(content.text()).toBe('Hello World'); - }); - - describe(' tags for Social Sharing', () => { - beforeEach(() => { - post = mount( - -

Hello World

-
- ); - }); - - it('should have a component', () => { - const helmet = post.find(Helmet); - - expect(helmet.length).toBe(1); - }); - - it('should have a tag for title', () => { - const helmet = Helmet.peek(); - - helmet.metaTags.filter((tag) => { - if (tag.property === 'og:title') { - expect(tag.content).toBe('The Greenhouse I/O - Blog'); - } - }); - }); - - it('should have a tag for type', () => { - const helmet = Helmet.peek(); - - helmet.metaTags.filter((tag) => { - if (tag.property === 'og:type') { - expect(tag.content).toBe('article'); - } - }); - }); - - it('should have a tag for url', () => { - const helmet = Helmet.peek(); - const slugDate = slugifyDate(mockPost.date); - - helmet.metaTags.filter((tag) => { - if (tag.property === 'og:url') { - expect(tag.content).toBe(`https://www.thegreenhouse.io/blog/${slugDate}`); - } - }); - }); - - it('should have a tag for image with a remote path', () => { - const helmet = Helmet.peek(); - - helmet.metaTags.filter((tag) => { - if (tag.property === 'og:image') { - expect(tag.content).toBe(`https://s3.amazonaws.com/www.thegreenhouse.io${mockPost.image}`); - } - }); - }); - - it('should have a tag for description', () => { - const helmet = Helmet.peek(); - - helmet.metaTags.filter((tag) => { - if (tag.property === 'og:description') { - expect(tag.content).toBe(mockPost.title); - } - }); - }); - }); - }); - - describe('Extended Meta Tag Functionality', () => { - const mockPost = { - title: 'Some title for this post', - date: '04.11.2018', - description: 'An optional description', - image: 'https://s3.amazonaws.com/uploads.thegreenhouse.io/project-evergreen/logo-small.png' - }; - - beforeEach(() => { - mount( - -

Hello World

-
- ); - }); - - it('should have a tag for image with a remote URL / path when provided an external URL', () => { - const helmet = Helmet.peek(); - - helmet.metaTags.filter((tag) => { - if (tag.property === 'og:description') { - expect(tag.content).toBe(mockPost.description); - } - }); - }); - }); - -}); \ No newline at end of file diff --git a/src/components/card-list/card-list.js b/src/components/card-list/card-list.js new file mode 100644 index 0000000..58315c1 --- /dev/null +++ b/src/components/card-list/card-list.js @@ -0,0 +1,37 @@ +import { html, LitElement } from 'lit-element'; + +import '../card/card'; + +class CardListComponent extends LitElement { + + constructor() { + super(); + } + + static get properties() { + return { + items: { + type: Array + } + }; + } + + render() { + let { items } = this; + + if (!items) { + items = []; + } + + const cards = items.map((item) => { + return html``; + }); + + return html` + ${ cards } + `; + } + +} + +customElements.define('app-card-list', CardListComponent); \ No newline at end of file diff --git a/src/components/card-list/card-list.jsx b/src/components/card-list/card-list.jsx deleted file mode 100644 index d1dc301..0000000 --- a/src/components/card-list/card-list.jsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import Card from '../card/card'; - -const CardList = (props) => { - - return ( -
- { - props.items.map(item => { - return ; - }) - } -
- ); -}; - -CardList.propTypes = { - items: PropTypes.array -}; - -CardList.defaultProps = { - items: [] -}; - -export default CardList; \ No newline at end of file diff --git a/src/components/card-list/card-list.spec.jsx b/src/components/card-list/card-list.spec.jsx deleted file mode 100644 index a6d148b..0000000 --- a/src/components/card-list/card-list.spec.jsx +++ /dev/null @@ -1,53 +0,0 @@ -import * as React from 'react'; -import { mount, configure } from 'enzyme'; -import Adapter from 'enzyme-adapter-react-15'; -import Card from '../card/card'; -import CardList from '../card-list/card-list'; - -configure({ adapter: new Adapter() }); - -describe('CardList Component', () => { - let cardList; - - beforeEach(() => { - cardList = mount(); - }); - - it('should not be null', () => { - expect(cardList).not.toBeNull(); - expect(cardList.find('.card-list').length).toEqual(1); - }); - - describe('Counting Cards', () => { - it('should display zero components when zero items are passed as props', () => { - const mockItems = []; - - cardList = mount(); - - expect(cardList.find().length).toEqual(mockItems.length); - }); - - // TODO fix why this count comes back as 0 - xit('should display two components when two items are passed as props', () => { - const mockItems = [{ - title: 'A PWA For Providence Geeks pt. 1', - abstract: `Part two in my performance focused series for the Providence Geeks website in which I go further into the technology used and the techniques - implemented to improve the performance and user exprience of the website.`, - link: 'https://medium.com/@thegreenhouseio/a-pwa-for-providence-geeks-a-case-study-in-performance-pt-2-4ba811b7be7', - img: 'https://cdn-images-1.medium.com/max/2000/1*Itn_4AfT13qgCN-YcsuMsQ.png', - date: '1/6/2018' - }, { - title: 'A PWA For Providence Geeks pt. 2', - abstract: 'The start of a multipart series...', - link: 'https://medium.com/@thegreenhouseio/a-pwa-for-pvd-geeks-a-case-study-in-performance-and-progressive-web-applications-pt-1-e8cc5c1d0f0a', - img: 'https://cdn-images-1.medium.com/max/2000/1*Itn_4AfT13qgCN-YcsuMsQ.png', - date: '1/6/2018' - }]; - - cardList = mount(); - - expect(cardList.find().length).toEqual(mockItems.length); - }); - - }); -}); \ No newline at end of file diff --git a/src/components/card/card.css b/src/components/card/card.css index a7785c1..141fca0 100644 --- a/src/components/card/card.css +++ b/src/components/card/card.css @@ -29,7 +29,7 @@ } .card-footer img { - + width: 100%; } @media (min-width: 500px) { diff --git a/src/components/card/card.js b/src/components/card/card.js new file mode 100644 index 0000000..2acdcdc --- /dev/null +++ b/src/components/card/card.js @@ -0,0 +1,73 @@ +import { html, LitElement } from 'lit-element'; + +import '../social-icon-link/social-icon-link'; +import cardCss from './card.css'; + +class CardComponent extends LitElement { + + constructor() { + super(); + } + + static get properties() { + return { + item: { + type: Object + } + }; + } + + render() { + const { item } = this; + const date = item.date + ? html`Date: ${item.date}` + : ''; + const slides = item.slides + ? html`📎 (slides)` + : ''; + const img = item.img + ? html`` + : ''; + const video = item.video + ? html`