forked from ampproject/amphtml
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
SizeList object in preparation to support sizes attribute
- Loading branch information
Dima Voytenko
committed
Oct 1, 2015
1 parent
4ad0a4e
commit cc735dc
Showing
4 changed files
with
321 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
/** | ||
* Copyright 2015 The AMP HTML Authors. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS-IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
import {assert} from './asserts'; | ||
import {assertLength} from './layout'; | ||
|
||
|
||
/** | ||
* A single option within a SizeList. | ||
* @typedef {{ | ||
* mediaQuery: (string|undefined), | ||
* size: (!Length) | ||
* }} | ||
*/ | ||
var SizeListOption; | ||
|
||
|
||
/** | ||
* Parses the text representation of "sizes" into SizeList object. | ||
* | ||
* There could be any number of size options within the SizeList. They are tried | ||
* in the order they were defined. The final size option must not have "media" | ||
* condition specified. All other size options must have "media" condition | ||
* specified. | ||
* | ||
* See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#Attributes | ||
* See http://www.w3.org/html/wg/drafts/html/master/semantics.html#attr-img-sizes | ||
* @param {string} s | ||
* @return {!SizeList} | ||
*/ | ||
export function parseSizeList(s) { | ||
let sSizes = s.split(','); | ||
assert(sSizes.length > 0, 'sizes has to have at least one size'); | ||
let sizes = []; | ||
sSizes.forEach((sSize) => { | ||
sSize = sSize.replace(/\s+/g, ' ').trim(); | ||
if (sSize.length == 0) { | ||
return; | ||
} | ||
|
||
let mediaStr; | ||
let sizeStr; | ||
let spaceIndex = sSize.lastIndexOf(' '); | ||
if (spaceIndex != -1) { | ||
mediaStr = sSize.substring(0, spaceIndex).trim(); | ||
sizeStr = sSize.substring(spaceIndex + 1).trim(); | ||
} else { | ||
sizeStr = sSize; | ||
mediaStr = undefined; | ||
} | ||
sizes.push({mediaQuery: mediaStr, size: assertLength(sizeStr)}); | ||
}); | ||
return new SizeList(sizes); | ||
}; | ||
|
||
|
||
/** | ||
* A SizeList object contains one or more sizes as typically seen in "sizes" | ||
* attribute. | ||
* | ||
* See "select" method for details on how the size selection is performed. | ||
* | ||
* See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#Attributes | ||
* See http://www.w3.org/html/wg/drafts/html/master/semantics.html#attr-img-sizes | ||
*/ | ||
export class SizeList { | ||
/** | ||
* @param {!Array<!SizeListOption>} sizes | ||
*/ | ||
constructor(sizes) { | ||
assert(sizes.length > 0, 'SizeList must have at least one option'); | ||
/** @private @const {!Array<!SizeListOption>} */ | ||
this.sizes_ = sizes; | ||
|
||
// All sources except for last must have a media query. The last one must | ||
// not. | ||
for (let i = 0; i < sizes.length; i++) { | ||
let option = sizes[i]; | ||
if (i < sizes.length - 1) { | ||
assert(option.mediaQuery, | ||
'All options except for the last must have a media condition'); | ||
} else { | ||
assert(!option.mediaQuery, | ||
'The last option must not have a media condition'); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Selects the first size that matches media conditions. If no options match, | ||
* the last option is returned. | ||
* | ||
* See http://www.w3.org/html/wg/drafts/html/master/semantics.html#attr-img-sizes | ||
* @param {!Window} win | ||
* @return {!Length} | ||
*/ | ||
select(win) { | ||
for (let i = 0; i < this.sizes_.length - 1; i++) { | ||
let option = this.sizes_[i]; | ||
if (win.matchMedia(option.mediaQuery).matches) { | ||
return option.size; | ||
} | ||
} | ||
return this.getLast(); | ||
} | ||
|
||
/** | ||
* Returns the last size in the SizeList, which is the default. | ||
* @return {!Length} | ||
*/ | ||
getLast() { | ||
return this.sizes_[this.sizes_.length - 1].size; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
/** | ||
* Copyright 2015 The AMP HTML Authors. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS-IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
import {SizeList, parseSizeList} from '../../src/size-list'; | ||
|
||
|
||
describe('SizeList parseSizeList', () => { | ||
|
||
it('should accept single option', () => { | ||
var res = parseSizeList(' \n 111px \n '); | ||
expect(res.sizes_.length).to.equal(1); | ||
expect(res.sizes_[0].mediaQuery).to.equal(undefined); | ||
expect(res.sizes_[0].size).to.equal('111px'); | ||
}); | ||
|
||
it('should accept multiple options', () => { | ||
var res = parseSizeList(' \n print 222px \n, 111px \n'); | ||
expect(res.sizes_.length).to.equal(2); | ||
expect(res.sizes_[0].mediaQuery).to.equal('print'); | ||
expect(res.sizes_[0].size).to.equal('222px'); | ||
expect(res.sizes_[1].mediaQuery).to.equal(undefined); | ||
expect(res.sizes_[1].size).to.equal('111px'); | ||
}); | ||
|
||
it('should accept even more multiple options', () => { | ||
var res = parseSizeList(' \n screen 333px, print 222px \n, 111px \n'); | ||
expect(res.sizes_.length).to.equal(3); | ||
expect(res.sizes_[0].mediaQuery).to.equal('screen'); | ||
expect(res.sizes_[0].size).to.equal('333px'); | ||
expect(res.sizes_[1].mediaQuery).to.equal('print'); | ||
expect(res.sizes_[1].size).to.equal('222px'); | ||
expect(res.sizes_[2].mediaQuery).to.equal(undefined); | ||
expect(res.sizes_[2].size).to.equal('111px'); | ||
}); | ||
|
||
it('should accept complicated media conditions', () => { | ||
var res = parseSizeList( | ||
' \n screen and (min-width: 1000px) \t ' + | ||
' and (max-width: 2000px) 222px \n,' + | ||
' 111px \n'); | ||
expect(res.sizes_.length).to.equal(2); | ||
expect(res.sizes_[0].mediaQuery).to.equal( | ||
'screen and (min-width: 1000px) and (max-width: 2000px)'); | ||
expect(res.sizes_[0].size).to.equal('222px'); | ||
expect(res.sizes_[1].mediaQuery).to.equal(undefined); | ||
expect(res.sizes_[1].size).to.equal('111px'); | ||
}); | ||
|
||
it('should accept different length units', () => { | ||
var res = parseSizeList(' \n 111vw \n '); | ||
expect(res.sizes_.length).to.equal(1); | ||
expect(res.sizes_[0].mediaQuery).to.equal(undefined); | ||
expect(res.sizes_[0].size).to.equal('111vw'); | ||
}); | ||
|
||
it('should fail bad length', () => { | ||
expect(() => { | ||
parseSizeList(' \n 111 \n '); | ||
}).to.throw(/Invalid length value/); | ||
}); | ||
}); | ||
|
||
|
||
describe('SizeList construct', () => { | ||
|
||
it('should have at least one option', () => { | ||
expect(() => { | ||
new SizeList([]); | ||
}).to.throw(/SizeList must have at least one option/); | ||
}); | ||
|
||
it('the last option must not have a query', () => { | ||
expect(() => { | ||
new SizeList([{mediaQuery: 'screen', size: '111px'}]); | ||
}).to.throw(/The last option must not have a media condition/); | ||
expect(() => { | ||
new SizeList([{mediaQuery: 'print', size: '222px'}, | ||
{mediaQuery: 'screen', size: '111px'}]); | ||
}).to.throw(/The last option must not have a media condition/); | ||
}); | ||
|
||
it('non-last options must have media query', () => { | ||
expect(() => { | ||
new SizeList([{size: '222px'}, {size: '111px'}]); | ||
}).to.throw(/All options except for the last must have a media condition/); | ||
}); | ||
}); | ||
|
||
|
||
describe('SizeList select', () => { | ||
it('should select default last option', () => { | ||
let sizeList = new SizeList([ | ||
{mediaQuery: 'media1', size: '444px'}, | ||
{mediaQuery: 'media2', size: '333px'}, | ||
{mediaQuery: 'media3', size: '222px'}, | ||
{size: '111px'} | ||
]); | ||
expect(sizeList.select({matchMedia: () => { | ||
return {}; | ||
}})).to.equal('111px'); | ||
}); | ||
|
||
it('should select a matching option', () => { | ||
let sizeList = new SizeList([ | ||
{mediaQuery: 'media1', size: '444px'}, | ||
{mediaQuery: 'media2', size: '333px'}, | ||
{mediaQuery: 'media3', size: '222px'}, | ||
{size: '111px'} | ||
]); | ||
expect(sizeList.select({matchMedia: (mq) => { | ||
if (mq == 'media2') { | ||
return {matches: true}; | ||
} | ||
return {}; | ||
}})).to.equal('333px'); | ||
}); | ||
|
||
it('should select first matching option', () => { | ||
let sizeList = new SizeList([ | ||
{mediaQuery: 'media1', size: '444px'}, | ||
{mediaQuery: 'media2', size: '333px'}, | ||
{mediaQuery: 'media3', size: '222px'}, | ||
{size: '111px'} | ||
]); | ||
expect(sizeList.select({matchMedia: (mq) => { | ||
if (mq == 'media1' || mq == 'media2') { | ||
return {matches: true}; | ||
} | ||
return {}; | ||
}})).to.equal('444px'); | ||
}); | ||
}); |