diff --git a/.babelrc b/.babelrc
index 9224ca318..eafcfa0d6 100644
--- a/.babelrc
+++ b/.babelrc
@@ -7,11 +7,6 @@
"transform-react-display-name"
],
"env": {
- "test": {
- "plugins": [
- "rewire"
- ]
- },
"development": {
"plugins": [
"typecheck"
diff --git a/.eslintrc b/.eslintrc
index a8d2f2fb3..a277bf4a8 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -50,6 +50,8 @@
"webpackIsomorphicTools": true,
ga: true,
Raven: true,
- mixpanel: true
+ mixpanel: true,
+ "expect": true,
+ "browser": true
}
}
diff --git a/nightwatch.js b/nightwatch.js
deleted file mode 100644
index eddf548a7..000000000
--- a/nightwatch.js
+++ /dev/null
@@ -1,11 +0,0 @@
-require('dotenv').load();
-require('app-module-path').addPath(__dirname);
-require('app-module-path').addPath('./src/scripts');
-
-require("babel-register");
-
-global.__CLIENT__ = false;
-global.__SERVER__ = true;
-
-
-require('nightwatch/bin/runner.js');
diff --git a/nightwatch.json b/nightwatch.json
deleted file mode 100644
index 09b59063f..000000000
--- a/nightwatch.json
+++ /dev/null
@@ -1,78 +0,0 @@
-{
- "src_folders": ["tests/functional/specs"],
- "output_folder": "tests/functional/output",
- "custom_commands_path": "tests/functional/commands",
- "custom_assertions_path": "tests/functional/assertions",
- "page_objects_path": "tests/functional/pages",
- "globals_path": "tests/functional/globals.js",
- "selenium" : {
- "start_process" : true,
- "server_path" : "node_modules/selenium-server/lib/runner/selenium-server-standalone-2.52.0.jar",
- "log_path" : "",
- "host" : "127.0.0.1",
- "port" : 5555,
- "cli_args" : {
- "webdriver.chrome.driver" : "node_modules/chromedriver/lib/chromedriver/chromedriver"
- }
- },
- "test_settings" : {
- "default" : {
- "exclude": ["./pages", "./commands"],
- "filter": "*.spec.js",
- "launch_url" : "http://localhost",
- "selenium_port" : 5555,
- "selenium_host" : "localhost",
- "silent": true,
- "screenshots" : {
- "enabled" : true,
- "path" : "tests/functional/screenshots"
- },
- "desiredCapabilities": {
- "browserName": "chrome",
- "javascriptEnabled": true,
- "acceptSslCerts": true
- }
- },
- "production" : {
- "exclude": ["./pages", "./commands"],
- "filter": "*.spec.js",
- "launch_url" : "http://localhost",
- "selenium_port" : 5555,
- "selenium_host" : "localhost",
- "silent": true,
- "screenshots" : {
- "enabled" : true,
- "path" : "tests/functional/screenshots"
- },
- "desiredCapabilities": {
- "browserName" : "phantomjs",
- "javascriptEnabled" : true,
- "acceptSslCerts" : true,
- "phantomjs.binary.path" : "/usr/local/phantomjs/bin/phantomjs"
- }
- },
- "mobile": {
- "exclude": ["./pages", "./commands"],
- "filter": "**/*.spec.js",
- "launch_url" : "http://localhost",
- "selenium_port" : 5555,
- "selenium_host" : "localhost",
- "silent": true,
- "screenshots" : {
- "enabled" : true,
- "path" : "tests/functional/screenshots"
- },
- "desiredCapabilities": {
- "browserName": "chrome",
- "javascriptEnabled": true,
- "acceptSslCerts": true,
- "chromeOptions": {
- "args": [
- "--user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 7_0_2 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A4449d Safari/9537.53",
- "--window-size=320,568"
- ]
- }
- }
- }
- }
-}
diff --git a/package.json b/package.json
index 46366c901..120cdff41 100644
--- a/package.json
+++ b/package.json
@@ -3,13 +3,18 @@
"version": "1.0.0",
"private": false,
"scripts": {
- "test": "./node_modules/karma/bin/karma start",
+ "test": "npm run test:dev:unit",
"test:ci:unit": "./node_modules/karma/bin/karma start --browsers PhantomJS --single-run; npm run test:ci:lint",
- "test:ci:functional": "node ./nightwatch.js -c ./nightwatch.json -e production",
+ "test:ci:functional": "BROWSER=phantomjs bash tests/functional/test.sh start-ci",
+ "posttest:ci:functional": "bash tests/functional/test.sh stop",
+ "test:ci:unit": "karma start --browsers PhantomJS --single-run; npm run test:ci:lint",
+ "test:dev:unit": "karma start",
"test:ci:lint": "eslint ./src/**/*.js",
"test:dev:unit": "./node_modules/karma/bin/karma start",
- "test:dev:functional": "node ./nightwatch.js -c ./nightwatch.json",
+ "test:dev:functional": "BROWSER=chrome bash tests/functional/test.sh start",
+ "posttest:dev:functional": "bash tests/functional/test.sh stop",
"test:dev:lint": "eslint ./src/scripts/**/*.js",
+ "test:stylelint": "stylelint './src/**/*.scss' --config webpack/.stylelintrc",
"dev": "node webpack/dev-server.js & PORT=8000 node start.js",
"start": "NODE_PATH=\"./src\" node ./start",
"build": "node ./node_modules/webpack/bin/webpack.js --config webpack/prod.config.js",
@@ -134,11 +139,12 @@
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^1.6.0",
"mocha": "^2.2.5",
- "nightwatch": "^0.8.6",
"nodemon": "^1.7.1",
"path": "^0.11.14",
+ "phantomjs": "^1.9.20",
"phantomjs-polyfill": "0.0.1",
"piping": "^0.3.0",
+ "pre-commit": "^1.1.3",
"react-addons-test-utils": "^0.14.7",
"react-transform-catch-errors": "^1.0.0",
"react-transform-hmr": "^1.0.1",
@@ -149,11 +155,14 @@
"selenium-server": "^2.48.2",
"sinon": "^1.15.3",
"sinon-chai": "^2.8.0",
+ "stylelint-webpack-plugin": "^0.2.0",
+ "wdio-mocha-framework": "^0.3.7",
+ "wdio-spec-reporter": "0.0.3",
+ "webdriverio": "4.2.1",
"webpack-dev-server": "^1.6.5"
},
"pre-commit": [
- "lint",
- "validate",
- "test"
+ "test:dev:lint",
+ "test:stylelint"
]
}
diff --git a/src/components/Ayah/index.js b/src/components/Ayah/index.js
index bbb7aad26..193522f36 100644
--- a/src/components/Ayah/index.js
+++ b/src/components/Ayah/index.js
@@ -20,6 +20,7 @@ export default class Ayah extends Component {
ayah: PropTypes.object.isRequired,
match: PropTypes.array,
isSearch: PropTypes.bool,
+ isPlaying: PropTypes.bool,
tooltip: PropTypes.string,
currentWord: PropTypes.any, // gets passed in an integer, null by default
onWordClick: PropTypes.func,
@@ -46,11 +47,14 @@ export default class Ayah extends Component {
}
handlePlay(ayah) {
- const {stop, setAyah, start} = this.props.actions;
+ const {isPlaying, actions} = this.props;
+ const {pause, setAyah, play} = actions;
- stop();
+ if (isPlaying) {
+ pause();
+ }
setAyah(ayah);
- start();
+ play();
}
renderTranslations() {
diff --git a/src/components/Ayah/style.scss b/src/components/Ayah/style.scss
index f76320937..11c5b7089 100644
--- a/src/components/Ayah/style.scss
+++ b/src/components/Ayah/style.scss
@@ -91,7 +91,7 @@
b{
float: right;
- border-color:: transparent;
+ border-color: transparent;
border-width: 0px 0px 1px 0px;
border-style: solid;
&.active {
diff --git a/src/config.js b/src/config.js
index 1e21ea8c4..eec53bbc6 100644
--- a/src/config.js
+++ b/src/config.js
@@ -79,7 +79,7 @@ module.exports = Object.assign({
],
style: [
{cssText: `@font-face {font-family: 'bismillah';
- src: url('http://quran-1f14.kxcdn.com/fonts/ttf/bismillah.ttf') format('truetype')}
+ src: url('//quran-1f14.kxcdn.com/fonts/ttf/bismillah.ttf') format('truetype')}
.bismillah{font-family: 'bismillah'; font-size: 36px !important; color: #000; padding: 25px 0px;}` // eslint-disable-line max-len
}
]
diff --git a/src/containers/Surah/Title/index.js b/src/containers/Surah/Title/index.js
index cef83c594..6b95a2472 100644
--- a/src/containers/Surah/Title/index.js
+++ b/src/containers/Surah/Title/index.js
@@ -13,7 +13,6 @@ const zeroPad = (num, places) => {
const Title = ({ surah }) => {
const title = require(`../../../../static/images/titles/${zeroPad(surah.id, 3)}.svg`); // eslint-disable-line global-require,max-len
-
return (
@@ -23,7 +22,7 @@ const Title = ({ surah }) => {
alt="Ornament left"
/>
{
- surah.id >= 1 &&
+ surah.id > 1 &&
{
{
- surah.id <= 114 &&
+ surah.id < 114 &&
', () => {
+
+ it('should render', () => {
+ wrapper = renderComponent(surah);
+ expect(wrapper).to.be.ok;
+ });
+
+ it('should not show previous surah if on the first surah', () => {
+ wrapper = renderComponent(surah);
+
+ const previous = wrapper.find('.previous-chapter').length;
+ const next = wrapper.find('.next-chapter').length;
+
+ expect(previous).to.equal(0);
+ expect(next).to.equal(1);
+ });
+
+ it('should not show next surah if on the last surah', () => {
+ surah.id = 114;
+ wrapper = renderComponent(surah);
+
+ const previous = wrapper.find('.previous-chapter').length;
+ const next = wrapper.find('.next-chapter').length;
+
+ expect(previous).to.equal(1);
+ expect(next).to.equal(0);
+ });
+
+ it('should show both next and previous if surah is neither last or first', () => {
+ surah.id = 14;
+ wrapper = renderComponent(surah);
+
+ const previous = wrapper.find('.previous-chapter').length;
+ const next = wrapper.find('.next-chapter').length;
+
+ expect(previous).to.equal(1);
+ expect(next).to.equal(1);
+ });
+});
+
+function renderComponent(data) {
+ return shallow(
);
+}
diff --git a/src/containers/Surah/index.js b/src/containers/Surah/index.js
index 1a39dd665..ce95999d2 100644
--- a/src/containers/Surah/index.js
+++ b/src/containers/Surah/index.js
@@ -64,7 +64,8 @@ class Surah extends Component {
options: PropTypes.object.isRequired,
params: PropTypes.object.isRequired,
ayahs: PropTypes.object.isRequired,
- isStarted: PropTypes.bool
+ isStarted: PropTypes.bool,
+ isPlaying: PropTypes.bool
};
state = {
@@ -280,7 +281,7 @@ class Surah extends Component {
}
renderAyahs() {
- const { ayahs, actions, options } = this.props; // eslint-disable-line no-shadow
+ const { ayahs, actions, options, isPlaying } = this.props; // eslint-disable-line no-shadow
return Object.values(ayahs).map(ayah => (
));
@@ -467,12 +469,12 @@ function mapStateToProps(state, ownProps) {
const surah: Object = state.surahs.entities[surahId];
const ayahs: Object = state.ayahs.entities[surahId];
const ayahIds = new Set(Object.keys(ayahs).map(key => parseInt(key.split(':')[1], 10)));
-
return {
surah,
ayahs,
ayahIds,
isStarted: state.audioplayer.isStarted,
+ isPlaying: state.audioplayer.isPlaying,
currentWord: state.ayahs.currentWord,
isEndOfSurah: ayahIds.size === surah.ayat,
surahs: state.surahs.entities,
diff --git a/src/styles/fonts/_fonts.scss b/src/styles/fonts/_fonts.scss
index b00d7cadb..c1ef8d838 100644
--- a/src/styles/fonts/_fonts.scss
+++ b/src/styles/fonts/_fonts.scss
@@ -1,4 +1,3 @@
-//@import url(http://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600|Montserrat);
@font-face{
font-family: 'Montserrat';
src: url('./static/fonts/montserrat/Montserrat-Regular.otf');
diff --git a/src/styles/main.scss b/src/styles/main.scss
index f83a5e3ff..f52f30248 100644
--- a/src/styles/main.scss
+++ b/src/styles/main.scss
@@ -1,6 +1,4 @@
-//@import 'bootstrap';
$icon-font-path: "~bootstrap-sass/assets/fonts/bootstrap/";
-//@import '~bootstrap-sass/assets/stylesheets/bootstrap';
@import 'mixins/center';
@import 'components/SurahNav';
@@ -44,38 +42,6 @@ a:hover{
display: none;
}
}
- .right-side{
- padding-top: 190px;
- width: 83.33333333%;
- float: right;
- display: block;
- position: relative;
- background-color: #fff;
- transition: width 0.5s;
- min-height: 100vh;
- z-index: 9999;
-
- .navbar-brand{
- display: none;
- }
-
- @media (max-width: $screen-sm-max) {
- width: 100%;
- padding-top: 150px;
-
- .navbar-brand{
- display: block;
- }
- }
-
- &.active{
- width: 99%;
-
- .navbar{
- width: 99; // This is almost like `noop` to get the css working
- }
- }
- }
}
.form-control:focus{
diff --git a/src/styles/partials/_navbar-brand.scss b/src/styles/partials/_navbar-brand.scss
index b73100571..33214c595 100644
--- a/src/styles/partials/_navbar-brand.scss
+++ b/src/styles/partials/_navbar-brand.scss
@@ -18,7 +18,6 @@
}
img {
display: inline !important;
- height: 100%;
&.logo{
height: 80%;
vertical-align: top;
diff --git a/src/styles/partials/_search-input.scss b/src/styles/partials/_search-input.scss
index b81eee249..494456dd9 100644
--- a/src/styles/partials/_search-input.scss
+++ b/src/styles/partials/_search-input.scss
@@ -27,7 +27,7 @@
padding: 15px;
height: 100%;
text-align: center;
- border: initial;
+ border: 0;
background: transparent;
right: 0px;
}
diff --git a/tests/functional/assertions/default.js b/tests/functional/assertions/default.js
deleted file mode 100644
index f053ebf79..000000000
--- a/tests/functional/assertions/default.js
+++ /dev/null
@@ -1 +0,0 @@
-module.exports = {};
diff --git a/tests/functional/commands/default.js b/tests/functional/commands/default.js
deleted file mode 100644
index f053ebf79..000000000
--- a/tests/functional/commands/default.js
+++ /dev/null
@@ -1 +0,0 @@
-module.exports = {};
diff --git a/tests/functional/pageobjects/BasePage.js b/tests/functional/pageobjects/BasePage.js
new file mode 100644
index 000000000..788b2c0d0
--- /dev/null
+++ b/tests/functional/pageobjects/BasePage.js
@@ -0,0 +1,16 @@
+export default class BasePage {
+ constructor(selectors) {
+ this.selectors = Object.assign(selectors, {
+ SURAH_PAGE_SURAH_NAME: '.surah-body .navbar-text.surah-name'
+ });
+ }
+
+ getSurahName() {
+ return browser.getText(this.selectors.SURAH_PAGE_SURAH_NAME);
+ }
+
+ goHome() {
+ browser.url('http://quran.com');
+ }
+
+};
diff --git a/tests/functional/pageobjects/HomePage.js b/tests/functional/pageobjects/HomePage.js
new file mode 100644
index 000000000..f0d282122
--- /dev/null
+++ b/tests/functional/pageobjects/HomePage.js
@@ -0,0 +1,33 @@
+import BasePage from './BasePage';
+import selectors from './selectors';
+
+export default class HomePage extends BasePage {
+ constructor() {
+ super(selectors);
+ this.selectors = selectors;
+ }
+
+ getLandingText() {
+ const landingText = browser.getText(this.selectors.LANDING_TEXT);
+ return landingText;
+ }
+
+ getNumberOfSurahs() {
+ const surahs = browser.elements(this.selectors.SURAH_LIST);
+ return surahs.value.length;
+ }
+
+ searchForSurahAndGoToSurahPage(searchQuery) {
+ browser.setValue(this.selectors.SEARCH_FORM, searchQuery);
+ browser.waitForVisible(this.selectors.SEARCH_RESULT_LIST);
+ browser.click(this.selectors.SEARCH_RESULT_LIST);
+ return this.getSurahName();
+ }
+
+ clickOntheSurahByNumber(number) {
+ const surahs = browser.elements('.row .col-xs-7 span');
+ const surahID = surahs.value[number].ELEMENT;
+ browser.elementIdClick(surahID);
+ return browser.getUrl();
+ }
+}
diff --git a/tests/functional/pageobjects/selectors.js b/tests/functional/pageobjects/selectors.js
new file mode 100644
index 000000000..1a8d66fb8
--- /dev/null
+++ b/tests/functional/pageobjects/selectors.js
@@ -0,0 +1,10 @@
+export default {
+ LANDING_TEXT: 'h4.title',
+ NEXT: 'a[data-direction="next"]',
+ PREVIOUS: 'a[data-direction="previous"]',
+ SURAH_LIST: '.row .col-md-4 li',
+ SEARCH_FORM: '.searchinput input',
+ SEARCH_RESULT_LIST: '.searchinput a',
+ SURAH_PAGE: '.surah-body .surah-title',
+ SURAH_NAME: '.navbar-text.surah-name'
+};
diff --git a/tests/functional/pages/index.js b/tests/functional/pages/index.js
deleted file mode 100644
index 498efda57..000000000
--- a/tests/functional/pages/index.js
+++ /dev/null
@@ -1,20 +0,0 @@
-module.exports = {
- url: 'http://localhost:8000',
- elements: {
- surahList: {
- selector: '.surah-list',
- },
- searchInput: {
- selector: '.searchinput'
- },
- indexHeader: {
- selector: '.index-header'
- },
- firstSurah: {
- selector: '.surah-list a[href="/1"]'
- },
- lastSurah: {
- selector: '.surah-list a[href="/114"]'
- }
- }
-};
diff --git a/tests/functional/specs/HomePageTests.js b/tests/functional/specs/HomePageTests.js
new file mode 100644
index 000000000..e860bde0b
--- /dev/null
+++ b/tests/functional/specs/HomePageTests.js
@@ -0,0 +1,27 @@
+import Homepage from '../pageobjects/HomePage';
+const homePage = new Homepage(browser);
+
+describe('Home Page', () => {
+
+ it('Should Have English Heading', () => {
+ homePage.goHome();
+ const text = homePage.getLandingText();
+ expect(text).to.equal('THE NOBLE QUR\'AN');
+ });
+
+ it('Should have 114 surahs', () => {
+ const numberSurahs = homePage.getNumberOfSurahs();
+ expect(numberSurahs).to.equal(114);
+ });
+
+ it('Should search for surah, click the first result item and land on the surah page', () => {
+ const surahName = homePage.searchForSurahAndGoToSurahPage('ya-sin');
+ expect(surahName).to.equal('YA-SIN (YA SIN) - سورة يس');
+ });
+
+ it('Should click a surah from the list of surahs and land on the surah page', () => {
+ homePage.goHome();
+ const url = homePage.clickOntheSurahByNumber(1);
+ expect(url).to.contain('/1');
+ });
+});
diff --git a/tests/functional/specs/Index.spec.js b/tests/functional/specs/Index.spec.js
deleted file mode 100644
index 29bec56a8..000000000
--- a/tests/functional/specs/Index.spec.js
+++ /dev/null
@@ -1,32 +0,0 @@
-module.exports = {
- 'can view index page': function(client) {
- var index = client.page.index();
-
- index.navigate()
- .waitForElementVisible('body', 2000)
- .assert.title("The Noble Qur'an - القرآن الكريم");
-
- index.expect.element('@indexHeader').to.be.present;
- index.expect.element('@surahList').to.be.present;
- index.expect.element('@indexHeader').to.be.present;
-
- client.end();
- },
-
- 'can view surah list': function(client) {
- var index = client.page.index();
-
- index.navigate();
-
- index.expect.element('@surahList').to.be.present;
- index.expect.element('@firstSurah').to.be.present;
- index.expect.element('@lastSurah').to.be.present;
-
- index.expect.element('@firstSurah').text.to.contain('The Opener');
- index.expect.element('@firstSurah').text.to.contain('Al-Fatihah');
- index.expect.element('@lastSurah').text.to.contain('The Mankind');
- index.expect.element('@lastSurah').text.to.contain('An-Nas');
-
- client.end();
- }
-}
diff --git a/tests/functional/test.sh b/tests/functional/test.sh
new file mode 100755
index 000000000..16b55a933
--- /dev/null
+++ b/tests/functional/test.sh
@@ -0,0 +1,26 @@
+
+#!/bin/bash
+if [ "$1" == 'stop' ]; then
+ echo "Stopping selenium server..."
+ pid=`ps -eo pid,args | grep selenium-server-standalone | grep -v grep | cut -c1-6`
+ echo $pid
+ kill $pid
+elif [ "$1" == 'start' ]; then
+ echo "Starting selenium server..."
+ PATH="./node_modules/phantomjs/bin:$PATH" java -jar ./node_modules/selenium-server/lib/runner/selenium-server-standalone-2.53.0.jar &> /dev/null &
+ sleep 2
+ BROWSERNAME=$BROWSER ./node_modules/.bin/wdio ./tests/functional/wdio.conf.js
+elif [ "$1" == 'start-ci' ]; then
+ echo "[CI] Starting selenium server..."
+
+ if [ ! -f selenium-server-standalone-2.53.1.jar ]; then
+ echo "selenium jar File not found!"
+ wget https://selenium-release.storage.googleapis.com/2.53/selenium-server-standalone-2.53.1.jar
+ fi
+
+ PATH="./node_modules/phantomjs/bin:$PATH" java -jar selenium-server-standalone-2.53.1.jar &> /dev/null &
+ sleep 5
+ BROWSERNAME=$BROWSER ./node_modules/.bin/wdio ./tests/functional/wdio.conf.js
+else
+ echo "Nothing to do!"
+fi
diff --git a/tests/functional/wdio.conf.js b/tests/functional/wdio.conf.js
new file mode 100644
index 000000000..b02f41928
--- /dev/null
+++ b/tests/functional/wdio.conf.js
@@ -0,0 +1,44 @@
+const browser = process.env.BROWSERNAME || 'phantomjs';
+
+exports.config = {
+
+ maxInstances: 4,
+ sync: true,
+ specs: [
+ './tests/functional/specs/**/*.js'
+ ],
+ exclude: [
+ ],
+
+ capabilities: [
+ {
+ browserName: browser
+ }
+ ],
+
+ logLevel: 'error',
+ coloredLogs: true,
+ screenshotPath: './errorShots/',
+
+ waitforTimeout: 10000,
+ framework: 'mocha',
+ reporters: ['dot', 'spec'],
+ onPrepare: () => {
+ },
+ before: () => {
+
+ const chai = require('chai'); // eslint-disable-line global-require
+ global.chai = chai;
+ global.expect = chai.expect;
+
+ process.on('unhandledRejection', (e) => {
+ console.error(e);
+ if (e.stack) {
+ console.error(e.stack);
+ }
+ });
+
+ require('babel-core/register'); // eslint-disable-line global-require
+ require('babel-polyfill'); // eslint-disable-line global-require
+ }
+};
diff --git a/webpack/.stylelintrc b/webpack/.stylelintrc
new file mode 100644
index 000000000..78c5d9e14
--- /dev/null
+++ b/webpack/.stylelintrc
@@ -0,0 +1,7 @@
+{
+ "rules": {
+ "unit-no-unknown": true,
+ "no-duplicate-selectors": true,
+ "block-no-empty": true
+ }
+}