Commit f2356c68 authored by Simon Welsh's avatar Simon Welsh

Finish of responsive grid in fnd:row and fnd:col

parent 2cc95e06
......@@ -10,12 +10,12 @@ echo <x:doctype>
<script src="js/modernizr.js"></script>
</head>
<body>
<fnd:row>
<fnd:col small="2">
<h1>Hello!</h1>
<fnd:row collapse={true}>
<fnd:col medium-offset={2} small="3">
<h1 class="panel">Hello!</h1>
</fnd:col>
<fnd:col small="3" large-offset="2" end="true">
<h1>World!</h1>
<fnd:col small="4" end="true">
<h1 class="panel">World!</h1>
</fnd:col>
</fnd:row>
<script src="js/jquery.js"></script>
......
......@@ -8,6 +8,16 @@ abstract class :fnd:base extends :x:element {
ConstSet<string> show;
private bool $_rendered = false;
private static ImmSet<string> $_specialAttributes = ImmSet {'data', 'aria'};
protected static ImmSet<string> $sizes = ImmSet {
'small',
'medium',
'large',
};
protected static ImmSet<string> $not_sizes = ImmSet {
'!small',
'!medium',
'!large',
};
abstract protected function compose(): ?XHPRoot;
/**
* Override compose() instead of this method for your content.
......
......@@ -5,12 +5,33 @@ class :fnd:col extends :fnd:base {
:div,
bool end = false,
int small, int medium, int large,
int small-offset, int medium-offset, int large-offset;
int small-offset, int medium-offset, int large-offset,
int small-push, int medium-push, int large-push,
int small-pull, int medium-pull, int large-pull,
bool medium-reset-order = false, bool large-reset-order = false,
mixed centered;
protected function compose(): :div {
$this->addClass('column');
// Column sizes
$this->addSizes();
$this->addOffsets();
$this->addPushes();
$this->addPulls();
$this->addResetOrder();
$this->addCentered($this->:centered);
// end
if ($this->:end) {
$this->addClass('end');
}
return <div>
{$this->getChildren()}
</div>;
}
private function addSizes(): void {
$small = $this->:small;
if ($small !== null) {
invariant(
......@@ -37,8 +58,9 @@ class :fnd:col extends :fnd:base {
);
$this->addClass('large-' . $large);
}
}
// Column offsets
private function addOffsets(): void{
$smallOffset = $this->:small-offset;
if ($smallOffset !== null) {
invariant(
......@@ -65,14 +87,116 @@ class :fnd:col extends :fnd:base {
);
$this->addClass('large-offset-' . $largeOffset);
}
}
// end
if ($this->:end) {
$this->addClass('end');
private function addPushes(): void{
$smallPush = $this->:small-push;
if ($smallPush !== null) {
invariant(
$smallPush > 0 && $smallPush < 12,
'Small push size must be in the range 1-11',
);
$this->addClass('small-push-' . $smallPush);
}
return <div>
{$this->getChildren()}
</div>;
$mediumPush = $this->:medium-push;
if ($mediumPush !== null) {
invariant(
$mediumPush > 0 && $mediumPush < 12,
'Medium push size must be in the range 1-11',
);
$this->addClass('medium-push-' . $mediumPush);
}
$largePush = $this->:large-push;
if ($largePush !== null) {
invariant(
$largePush > 0 && $largePush < 12,
'Large push size must be in the range 1-11',
);
$this->addClass('large-push-' . $largePush);
}
}
private function addPulls(): void{
$smallPull = $this->:small-pull;
if ($smallPull !== null) {
invariant(
$smallPull > 0 && $smallPull < 12,
'Small pull size must be in the range 1-11',
);
$this->addClass('small-pull-' . $smallPull);
}
$mediumPull = $this->:medium-pull;
if ($mediumPull !== null) {
invariant(
$mediumPull > 0 && $mediumPull < 12,
'Medium pull size must be in the range 1-11',
);
$this->addClass('medium-pull-' . $mediumPull);
}
$largePull = $this->:large-pull;
if ($largePull !== null) {
invariant(
$largePull > 0 && $largePull < 12,
'Large pull size must be in the range 1-11',
);
$this->addClass('large-pull-' . $largePull);
}
}
private function addResetOrder(): void {
if ($this->:medium-reset-order) {
$this->addClass('medium-reset-order');
}
if ($this->:large-reset-order) {
$this->addClass('large-reset-order');
}
}
private function addCentered(mixed $centered): void {
if ($centered === null) {
return;
}
if (is_string($centered)) {
if (self::$sizes->contains($centered)) {
$this->addClass($centered . '-centered');
} else if (self::$not_sizes->contains($centered)) {
$centered = substr($centered, 1);
$this->addClass($centered . '-uncentered');
} else {
throw new XHPInvalidAttributeException(
$this,
self::_getEnumsString(),
'centered',
$centered,
);
}
return;
}
if ($centered instanceof Traversable) {
foreach ($centered as $value) {
$this->addCentered($value);
}
return;
}
throw new XHPInvalidAttributeException(
$this,
'Traversable|' . self::_getEnumsString(),
'centered',
$centered,
);
}
<<__Memoize>>
private static function _getEnumsString(): string {
return sprintf(
'enum("%s", "%s")',
implode('", "', self::$sizes),
implode('", "', self::$not_sizes),
);
}
}
......@@ -4,12 +4,71 @@ class :fnd:row extends :fnd:base {
children (:fnd:col)*;
attribute
:div;
:div,
mixed collapse;
protected function compose(): :div {
$this->addClass('row');
$this->addCollapse($this->:collapse);
return <div>
{$this->getChildren()}
</div>;
}
private function addCollapse(mixed $collapse): void {
if ($collapse === null) {
return;
}
if (is_bool($collapse)) {
if ($collapse) {
$this->addClass('collapse');
} else {
$this->addClass('uncollapse');
}
return;
}
if (is_string($collapse)) {
if ($collapse == 'true') {
$this->addClass('collapse');
} else if ($collapse == 'false') {
$this->addClass('uncollapse');
} else if (self::$sizes->contains($collapse)) {
$this->addClass($collapse . '-collapse');
} else if (self::$not_sizes->contains($collapse)) {
$collapse = substr($collapse, 1);
$this->addClass($collapse . '-uncollapse');
} else {
throw new XHPInvalidAttributeException(
$this,
self::_getEnumsString(),
'collapse',
$collapse,
);
}
return;
}
if ($collapse instanceof Traversable) {
foreach ($collapse as $value) {
$this->addCollapse($value);
}
return;
}
throw new XHPInvalidAttributeException(
$this,
'bool|Traversable|' . self::_getEnumsString(),
'collapse',
$collapse,
);
}
<<__Memoize>>
private static function _getEnumsString(): string {
return sprintf(
'enum("collapse", "uncollapse", "%s", "%s")',
implode('", "', self::$sizes),
implode('", "', self::$not_sizes),
);
}
}
<!-- We bootstrap the base class so that it doesn't need to be autoloaded -->
<phpunit bootstrap="./tests/FndTestBase.php">
<testsuites>
<testsuite>
......
<?hh
<?hh // strict
class ColTest extends FndTestBase {
public function testCompose(): void {
......@@ -175,4 +175,257 @@ class ColTest extends FndTestBase {
$rendered = $this->render($col);
$this->assertHasClass($rendered, 'end');
}
/**
* @depends testCompose
*/
public function testCenteredSizes(): void {
$col = <fnd:col centered="large" />;
$rendered = $this->render($col);
$this->assertOnlyClasses($rendered, ['large-centered', 'column']);
$col = <fnd:col centered="medium" />;
$rendered = $this->render($col);
$this->assertOnlyClasses($rendered, ['medium-centered', 'column']);
$col = <fnd:col centered="small" />;
$rendered = $this->render($col);
$this->assertOnlyClasses($rendered, ['small-centered', 'column']);
$col = <fnd:col centered="!large" />;
$rendered = $this->render($col);
$this->assertOnlyClasses($rendered, ['large-uncentered', 'column']);
$col = <fnd:col centered="!medium" />;
$rendered = $this->render($col);
$this->assertOnlyClasses($rendered, ['medium-uncentered', 'column']);
$col = <fnd:col centered="!small" />;
$rendered = $this->render($col);
$this->assertOnlyClasses($rendered, ['small-uncentered', 'column']);
}
/**
* @depends testCompose
*/
public function testCenteredMulti(): void {
$col = <fnd:col centered={ImmSet {'small', '!large'}} />;
$rendered = $this->render($col);
$this->assertOnlyClasses($rendered, ['large-uncentered', 'small-centered', 'column']);
$col = <fnd:col centered={['small', '!medium', 'large']} />;
$rendered = $this->render($col);
$this->assertOnlyClasses($rendered, ['small-centered', 'medium-uncentered', 'large-centered', 'column']);
}
public function testInvalidCentered(): void {
// Test invalid type
$col = <fnd:col centered={3} />;
try {
$this->render($col);
$this->fail('Rendered with invalid centered value');
} catch (XHPInvalidAttributeException $e) {
}
// Test invalid string
$col->setAttribute('centered', 'wrong');
try {
$this->render($col);
$this->fail('Rendered with invalid centered value');
} catch (XHPInvalidAttributeException $e) {
}
// Test nested invalid type
$col->setAttribute('centered', [3]);
try {
$this->render($col);
$this->fail('Rendered with invalid centered value');
} catch (XHPInvalidAttributeException $e) {
}
}
public function testPush(): void {
$col = <fnd:col small-push={2} medium-push={3} large-push={4} />;
$rendered = $this->render($col);
$this->assertHasClass($rendered, 'small-push-2');
$this->assertHasClass($rendered, 'medium-push-3');
$this->assertHasClass($rendered, 'large-push-4');
}
public function testInvalidPush(): void {
$col = <fnd:col small-push={0} />;
try {
$this->render($col);
$this->fail('Rendered with invalid push size');
} catch(InvariantException $e) {
$this->assertEquals(
'Small push size must be in the range 1-11',
$e->getMessage(),
);
}
$col->setAttribute('small-push', 12);
try {
$this->render($col);
$this->fail('Rendered with invalid push size');
} catch(InvariantException $e) {
$this->assertEquals(
'Small push size must be in the range 1-11',
$e->getMessage(),
);
}
$col = <fnd:col medium-push={0} />;
try {
$this->render($col);
$this->fail('Rendered with invalid push size');
} catch(InvariantException $e) {
$this->assertEquals(
'Medium push size must be in the range 1-11',
$e->getMessage(),
);
}
$col->setAttribute('medium-push', 12);
try {
$this->render($col);
$this->fail('Rendered with invalid push size');
} catch(InvariantException $e) {
$this->assertEquals(
'Medium push size must be in the range 1-11',
$e->getMessage(),
);
}
$col = <fnd:col large-push={0} />;
try {
$this->render($col);
$this->fail('Rendered with invalid push size');
} catch(InvariantException $e) {
$this->assertEquals(
'Large push size must be in the range 1-11',
$e->getMessage(),
);
}
$col->setAttribute('large-push', 12);
try {
$this->render($col);
$this->fail('Rendered with invalid push size');
} catch(InvariantException $e) {
$this->assertEquals(
'Large push size must be in the range 1-11',
$e->getMessage(),
);
}
}
public function testPull(): void {
$col = <fnd:col small-pull={2} medium-pull={3} large-pull={4} />;
$rendered = $this->render($col);
$this->assertHasClass($rendered, 'small-pull-2');
$this->assertHasClass($rendered, 'medium-pull-3');
$this->assertHasClass($rendered, 'large-pull-4');
}
public function testInvalidPull(): void {
$col = <fnd:col small-pull={0} />;
try {
$this->render($col);
$this->fail('Rendered with invalid pull size');
} catch(InvariantException $e) {
$this->assertEquals(
'Small pull size must be in the range 1-11',
$e->getMessage(),
);
}
$col->setAttribute('small-pull', 12);
try {
$this->render($col);
$this->fail('Rendered with invalid pull size');
} catch(InvariantException $e) {
$this->assertEquals(
'Small pull size must be in the range 1-11',
$e->getMessage(),
);
}
$col = <fnd:col medium-pull={0} />;
try {
$this->render($col);
$this->fail('Rendered with invalid pull size');
} catch(InvariantException $e) {
$this->assertEquals(
'Medium pull size must be in the range 1-11',
$e->getMessage(),
);
}
$col->setAttribute('medium-pull', 12);
try {
$this->render($col);
$this->fail('Rendered with invalid pull size');
} catch(InvariantException $e) {
$this->assertEquals(
'Medium pull size must be in the range 1-11',
$e->getMessage(),
);
}
$col = <fnd:col large-pull={0} />;
try {
$this->render($col);
$this->fail('Rendered with invalid pull size');
} catch(InvariantException $e) {
$this->assertEquals(
'Large pull size must be in the range 1-11',
$e->getMessage(),
);
}
$col->setAttribute('large-pull', 12);
try {
$this->render($col);
$this->fail('Rendered with invalid pull size');
} catch(InvariantException $e) {
$this->assertEquals(
'Large pull size must be in the range 1-11',
$e->getMessage(),
);
}
}
public function testResetOrder(): void {
$col = <fnd:col />;
$rendered = $this->render($col);
$this->assertNotHasClass($rendered, 'medium-reset-order');
$this->assertNotHasClass($rendered, 'large-reset-order');
$col = <fnd:col medium-reset-order={false} />;
$rendered = $this->render($col);
$this->assertNotHasClass($rendered, 'medium-reset-order');
$col = <fnd:col medium-reset-order={true} />;
$rendered = $this->render($col);
$this->assertHasClass($rendered, 'medium-reset-order');
$col = <fnd:col large-reset-order={false} />;
$rendered = $this->render($col);
$this->assertNotHasClass($rendered, 'large-reset-order');
$col = <fnd:col large-reset-order={true} />;
$rendered = $this->render($col);
$this->assertHasClass($rendered, 'large-reset-order');
}
}
<?hh
<?hh // strict
class FndTestBase extends PHPUnit_Framework_TestCase {
public function assertHasClass(XHPRoot $xhp, string $class) {
public function assertHasClass(XHPRoot $xhp, string $class): void {
/* HH_FIXME[4053] Need union types */
$classes = $xhp->:class;
$classes = explode(' ', $classes);
......@@ -9,7 +9,7 @@ class FndTestBase extends PHPUnit_Framework_TestCase {
$this->assertContains($class, $classes);
}
public function assertNotHasClass(XHPRoot $xhp, string $class) {
public function assertNotHasClass(XHPRoot $xhp, string $class): void {
/* HH_FIXME[4053] Need union types */
$classes = $xhp->:class;
$classes = explode(' ', $classes);
......@@ -17,7 +17,17 @@ class FndTestBase extends PHPUnit_Framework_TestCase {
$this->assertNotContains($class, $classes);
}
public function render(XHPRoot $xhp) {
public function assertOnlyClasses(XHPRoot $xhp, Traversable<string> $expected): void {
/* HH_FIXME[4053] Need union types */
$classes = $xhp->:class;
$classes = new Set(explode(' ', $classes));
$expected = new Set($expected);
$this->assertEquals($expected, $classes);
}
public function render(XHPRoot $xhp): XHPRoot {
$reflect = new ReflectionMethod($xhp, 'render');
$reflect->setAccessible(true);
return $reflect->invoke($xhp);
......
<?hh // strict
class RowTest extends FndTestBase {
public function testCompose(): void {
$row = <fnd:row />;
$rendered = $this->render($row);
$this->assertHasClass($rendered, 'row');
}
/**
* @depends testCompose
*/
public function testCollapseAll(): void {
$row = <fnd:row />;
$rendered = $this->render($row);
$this->assertOnlyClasses($rendered, ['row']);
$row = <fnd:row collapse={false} />;
$rendered = $this->render($row);
$this->assertOnlyClasses($rendered, ['uncollapse', 'row']);
$row = <fnd:row collapse="false" />;
$rendered = $this->render($row);
$this->assertOnlyClasses($rendered, ['uncollapse', 'row']);
$row = <fnd:row collapse={true} />;
$rendered = $this->render($row);
$this->assertOnlyClasses($rendered, ['collapse', 'row']);
$row = <fnd:row collapse="true" />;
$rendered = $this->render($row);
$this->assertOnlyClasses($rendered, ['collapse', 'row']);
}
/**
* @depends testCompose
*/
public function testCollapseSizes(): void {
$row = <fnd:row collapse="large" />;
$rendered = $this->render($row);
$this->assertOnlyClasses($rendered, ['large-collapse', 'row']);
$row = <fnd:row collapse="medium" />;
$rendered = $this->render($row);
$this->assertOnlyClasses($rendered, ['medium-collapse', 'row']);
$row = <fnd:row collapse="small" />;
$rendered = $this->render($row);
$this->assertOnlyClasses($rendered, ['small-collapse', 'row']);
$row = <fnd:row collapse="!large" />;
$rendered = $this->render($row);
$this->assertOnlyClasses($rendered, ['large-uncollapse', 'row']);
$row = <fnd:row collapse="!medium" />;
$rendered = $this->render($row);
$this->assertOnlyClasses($rendered, ['medium-uncollapse', 'row']);
$row = <fnd:row collapse="!small" />;
$rendered = $this->render($row);
$this->assertOnlyClasses($rendered, ['small-uncollapse', 'row']);
}
/**
* @depends testCompose
*/
public function testCollapseMulti(): void {
$row = <fnd:row collapse={ImmSet {'true', '!large'}} />;
$rendered = $this->render($row);
$this->assertOnlyClasses($rendered, ['large-uncollapse', 'collapse', 'row']);
$row = <fnd:row collapse={ImmSet {'false', 'large'}} />;
$rendered = $this->render($row);
$this->assertOnlyClasses($rendered, ['large-collapse', 'uncollapse', 'row']);
$row = <fnd:row collapse={['small', '!medium']} />;
$rendered = $this->render($row);
$this->assertOnlyClasses($rendered, ['small-collapse', 'medium-uncollapse', 'row']);
}
public function testInvalidCollapse(): void {
// Test invalid type
$row = <fnd:row collapse={3} />;
try {
$this->render($row);
$this->fail('Rendered with invalid collapse value');
} catch (XHPInvalidAttributeException $e) {
}
// Test invalid string
$row->setAttribute('collapse', 'wrong');
try {
$this->render($row);
$this->fail('Rendered with invalid collapse value');
} catch (XHPInvalidAttributeException $e) {
}
// Test nested invalid type
$row->setAttribute('collapse', [3]);
try {
$this->render($row);
$this->fail('Rendered with invalid collapse value');
} catch (XHPInvalidAttributeException $e) {
}
}
}
<?hh // decl
namespace {
class PHPUnit_Framework_TestCase {
public function assertEquals(mixed $expect, mixed $actual, ?string $msg = null);
public function assertNotEquals(mixed $expect, mixed $actual, ?string $msg = null);
public function assertContains(mixed $key, array $array, ?string $msg = null);
public function assertNotContains(mixed $key, array $array, ?string $msg = null);
public function fail(string $msg);
}
}
class InvariantException extends Exception {}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment