Skip to content

Instantly share code, notes, and snippets.

@alexgb
Forked from magistrula/components.my-face.js
Created September 11, 2018 20:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexgb/c46dae7e1d58d06919762d1e664ce4be to your computer and use it in GitHub Desktop.
Save alexgb/c46dae7e1d58d06919762d1e664ce4be to your computer and use it in GitHub Desktop.
Build-a-Face
import Ember from 'ember';
const { computed } = Ember;
export const FACE_COLOR_OPTIONS = [
{ value: 'salmon', label: 'red' },
{ value: 'orange', label: 'orange' },
{ value: 'yellow', label: 'yellow' },
{ value: 'palegreen', label: 'green' },
{ value: 'lightblue', label: 'blue' },
{ value: 'lavender', label: 'purple', isDefault: true },
{ value: 'pink', label: 'pink' },
{ value: 'lightgray', label: 'gray' }
];
export const HAT_COLOR_OPTIONS = [
{ value: 'crimson', label: 'red' },
{ value: 'orangered', label: 'orange' },
{ value: 'gold', label: 'yellow' },
{ value: 'mediumspringgreen', label: 'green' },
{ value: 'dodgerblue', label: 'blue', isDefault: true },
{ value: 'blueviolet', label: 'purple' },
{ value: 'fuchsia', label: 'pink' },
{ value: 'saddlebrown', label: 'brown' },
{ value: 'gray', label: 'gray' }
];
export default Ember.Component.extend({
classNames: ['MyFace'],
// This would normally be handled by ember-get-component
attributeBindings: ['data-test-component-name'],
['data-test-component-name']: 'my-face',
// internal params
faceColorOptions: null,
hatColorOptions: null,
eyeContent: null,
noseContent: null,
mouthContent: null,
faceColor: null,
hatColor: null,
pathToMessage: null,
noseMessage: 'I love to stop and smell the roses.',
mouthMessage: 'Feed me!',
leftEyeMessage: '<wink wink>',
rightEyeMessage: computed('activeFaceColorOption', function() {
const faceColorLabel = this.get('activeFaceColorOption.label');
return `I spy with my little eye something ${faceColorLabel}.`;
}),
message: computed('pathToMessage', 'activeFaceColorOption', function() {
const pathToMessage = this.get('pathToMessage');
return pathToMessage ?
this.get(pathToMessage) :
'(Click my eyes, nose, or mouth to see a special message)';
}),
isFaceComplete: computed.and('eyeContent', 'noseContent', 'mouthContent'),
faceStyle: computed('activeFaceColorOption', 'activeHatColorOption', function() {
const faceColor = this.get('activeFaceColorOption.value');
const hatColor = this.get('activeHatColorOption.value');
return Ember.String.htmlSafe(
`background-color: ${faceColor}; border-color: ${hatColor}`
);
}),
init() {
this._super(...arguments);
this._resetFace();
},
_resetFace() {
this.setProperties({
eyeContent: '**',
noseContent: '~~',
mouthContent: '\\=======/',
faceColorOptions: FACE_COLOR_OPTIONS,
hatColorOptions: HAT_COLOR_OPTIONS,
activeFaceColorOption: FACE_COLOR_OPTIONS.findBy('isDefault'),
activeHatColorOption: HAT_COLOR_OPTIONS.findBy('isDefault'),
pathToMessage: null
});
},
actions: {
setFaceColor(colorOption) {
this.set('activeFaceColorOption', colorOption);
},
setHatColor(colorOption) {
this.set('activeHatColorOption', colorOption);
},
showLeftEyeMessage() {
if (this.get('isFaceComplete')) {
this.set('pathToMessage', 'leftEyeMessage');
}
},
showRightEyeMessage() {
if (this.get('isFaceComplete')) {
this.set('pathToMessage', 'rightEyeMessage');
}
},
showNoseMessage() {
if (this.get('isFaceComplete')) {
this.set('pathToMessage', 'noseMessage');
}
},
showMouthMessage() {
if (this.get('isFaceComplete')) {
this.set('pathToMessage', 'mouthMessage');
}
},
clearFace() {
this._resetFace();
}
}
});
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['MyFaceMyEyes'],
// This would normally be handled by ember-get-component
attributeBindings: ['data-test-component-name'],
['data-test-component-name']: 'my-face/my-eyes',
});
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['MyFaceMyMouth']
});
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['MyFaceMyNose']
});
import Ember from 'ember';
export default Ember.Controller.extend({
appName: 'Build-a-Face'
});
body {
margin: 12px 16px;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 12pt;
}
.u-space-below {
margin-bottom: 15px;
}
.u-more-space-below {
margin-bottom: 20px;
}
.u-clickable {
cursor: pointer;
}
.MyFace {
display: flex;
flex-direction: column;
align-items: center;
}
.MyFace-face {
width: 200px;
height: 150px;
padding-top: 10px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 50%;
border-top: 60px solid;
border-left: 4px solid;
border-right: 4px solid;
text-align: center;
}
.MyFace-message {
height: 1em;
margin-bottom: 30px;
color: darkslategray;
font-style: italic;
}
.MyFace-buttonCategory {
margin-right: 5px;
font-size: .8em;
font-weight: bold;
}
.MyFace-colorButton.is-active {
color: navy;
font-weight: bold;
}
.MyFaceMyEyes {
width: 150px;
height: 1em;
display: flex;
justify-content: space-around;
color: blue;
}
.MyFaceMyNose {
height: 1em;
color: green;
}
.MyFaceMyMouth {
height: 1em;
color: red;
}
<div
class="MyFace-face u-space-below"
style={{faceStyle}}
data-test-face-content
>
{{my-face/my-eyes
class="u-space-below"
clickLeftEye=(action "showLeftEyeMessage")
clickRightEye=(action "showRightEyeMessage")
content=eyeContent
}}
{{my-face/my-nose
class="u-more-space-below"
click=(action "showNoseMessage")
content=noseContent
}}
{{my-face/my-mouth
click=(action "showMouthMessage")
content=mouthContent
}}
</div>
<div class="MyFace-message">
{{if isFaceComplete message}}
</div>
<div class="u-space-below">
{{input
value=eyeContent
placeholder="Eye text"
data-test-attr="MyFace-eyesInput"
}}
{{input
value=noseContent
placeholder="Nose text"
}}
{{input
value=mouthContent
placeholder="Mouth text"
}}
</div>
<div class="u-space-below">
<span class="MyFace-buttonCategory">
Face Color:
</span>
{{#each faceColorOptions as |option|}}
<button
class="MyFace-colorButton {{if (eq activeFaceColorOption option) "is-active"}}"
{{action "setFaceColor" option}}
data-test-face-color-button
>
{{option.label}}
</button>
{{/each}}
</div>
<div class="u-space-below">
<span class="MyFace-buttonCategory">
Hat Color:
</span>
{{#each hatColorOptions as |option|}}
<button
class="MyFace-colorButton {{if (eq activeHatColorOption option) "is-active"}}"
{{action "setHatColor" option}}
>
{{option.label}}
</button>
{{/each}}
</div>
<button {{action "clearFace"}}>
Reset Face
</button>
<span {{action clickLeftEye}} class="u-clickable" data-test-left-eye>
{{content}}
</span>
<span {{action clickRightEye}} class="u-clickable" data-test-right-eye>
{{content}}
</span>
<span class="u-clickable">
{{content}}
</span>
<span class="u-clickable">
{{content}}
</span>
import { run } from '@ember/runloop';
export default function destroyApp(application) {
run(application, 'destroy');
}
import Resolver from '../../resolver';
import config from '../../config/environment';
const resolver = Resolver.create();
resolver.namespace = {
modulePrefix: config.modulePrefix,
podModulePrefix: config.podModulePrefix
};
export default resolver;
import Ember from 'ember';
import Application from '../../app';
import config from '../../config/environment';
const { run } = Ember;
const assign = Ember.assign || Ember.merge;
export default function startApp(attrs) {
let application;
let attributes = assign({rootElement: "#test-root"}, config.APP);
attributes = assign(attributes, attrs); // use defaults, but you can override;
run(() => {
application = Application.create(attributes);
application.setupForTesting();
application.injectTestHelpers();
});
return application;
}
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
import {
FACE_COLOR_OPTIONS,
HAIR_COLOR_OPTIONS
} from 'app/components/my-face';
import {
attribute,
collection,
create,
fillable,
hasClass,
text
} from 'ember-cli-page-object';
import { alias, getter } from 'ember-cli-page-object/macros';
// -- PAGE OBJECT (START) --
const SELECTORS = {
COMPONENT: '[data-test-component-name="my-face"]',
EYES: '[data-test-component-name="my-face/my-eyes"]',
EYES_LEFT: '[data-test-left-eye]',
EYES_RIGHT: '[data-test-left-eye]',
EYES_INPUT: '[data-test-attr="MyFace-eyesInput"]',
FACE_COLOR_BUTTON: '[data-test-face-color-button]',
FACE_CONTENT: '[data-test-face-content]',
};
const MyFace = {
scope: SELECTORS.COMPONENT, // SCOPE
// pretend this eyes component is so complicated that it needs its own page object,
// which we would define in a separate file then import for use in this PO
eyes: {
scope: SELECTORS.EYES, // SCOPE
leftEyeText: text(SELECTORS.EYES_LEFT),
rightEyeText: text(SELECTORS.EYES_RIGHT),
hasSameTextForBothEyes: getter(function() { // GETTER
return this.leftEyeText === this.rightEyeText;
}),
},
eyesText: alias('eyes.leftEyeText'), // ALIAS
setEyesText: fillable(SELECTORS.EYES_INPUT),
faceStyle: attribute('style', SELECTORS.FACE_CONTENT),
faceColorButtons: collection({ // COLLECTION
itemScope: SELECTORS.FACE_COLOR_BUTTON, // SCOPE
item: {
isActive: hasClass('is-active')
}
})
};
const PO = create(MyFace);
// -- PAGE OBJECT (END) --
moduleForComponent('my-face', 'MyFaceComponent', {
integration: true,
beforeEach() {
PO.setContext(this);
},
afterEach() {
PO.removeContext();
}
});
test('starts out with default eye content', function(assert) {
this.render(hbs`{{my-face}}`);
assert.equal(PO.eyesText, '**');
});
test('can set the eyes content', function(assert) {
this.render(hbs`{{my-face}}`);
PO.setEyesText('!-!');
assert.equal(PO.eyesText, '!-!');
});
test('shows the expected # of face color buttons', function(assert) {
this.render(hbs`{{my-face}}`);
assert.equal(PO.faceColorButtons().count, FACE_COLOR_OPTIONS.length);
});
test('shows a user-facing label for each face color button', function(assert) {
this.render(hbs`{{my-face}}`);
FACE_COLOR_OPTIONS.forEach((option, index) => {
assert.equal(PO.faceColorButtons(index).text, FACE_COLOR_OPTIONS[index].label);
});
});
test('can set the face color', function(assert) {
this.render(hbs`{{my-face}}`);
PO.faceColorButtons(0).click();
const expectedStyling = `background-color: ${FACE_COLOR_OPTIONS[0].value}`;
assert.ok(PO.faceStyle.includes(expectedStyling));
});
test('styles the currently selected face color button', function(assert) {
this.render(hbs`{{my-face}}`);
PO.faceColorButtons(0).click();
assert.equal(false, true);
});
import resolver from './helpers/resolver';
import {
setResolver
} from 'ember-qunit';
import { start } from 'ember-cli-qunit';
setResolver(resolver);
start();
{
"version": "0.15.0",
"EmberENV": {
"FEATURES": {}
},
"options": {
"use_pods": false,
"enable-testing": false
},
"dependencies": {
"jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js",
"ember": "3.2.2",
"ember-template-compiler": "3.2.2",
"ember-testing": "3.2.2"
},
"addons": {
"ember-data": "2.12.2",
"ember-cli-page-object": "1.13.0",
"ember-test-selectors": "1.0.0",
"ember-truth-helpers": "1.2.0"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment