// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. import 'package:boolean_selector/src/scanner.dart'; import 'package:boolean_selector/src/token.dart'; import 'package:test/test.dart'; /// A matcher that asserts that a value is a [IdentifierToken]. const _isIdentifierToken = TypeMatcher(); void main() { group('peek()', () { test('returns the next token without consuming it', () { var scanner = Scanner('( )'); expect(scanner.peek().type, equals(TokenType.leftParen)); expect(scanner.peek().type, equals(TokenType.leftParen)); expect(scanner.peek().type, equals(TokenType.leftParen)); }); test('returns an end-of-file token at the end of a file', () { var scanner = Scanner('( )'); scanner.next(); scanner.next(); var token = scanner.peek(); expect(token.type, equals(TokenType.endOfFile)); expect(token.span.start.offset, equals(3)); expect(token.span.end.offset, equals(3)); }); test('throws a StateError if called after end-of-file was consumed', () { var scanner = Scanner('( )'); scanner.next(); scanner.next(); scanner.next(); expect(() => scanner.peek(), throwsStateError); }); }); group('next()', () { test('consumes and returns the next token', () { var scanner = Scanner('( )'); expect(scanner.next().type, equals(TokenType.leftParen)); expect(scanner.peek().type, equals(TokenType.rightParen)); expect(scanner.next().type, equals(TokenType.rightParen)); }); test('returns an end-of-file token at the end of a file', () { var scanner = Scanner('( )'); scanner.next(); scanner.next(); var token = scanner.next(); expect(token.type, equals(TokenType.endOfFile)); expect(token.span.start.offset, equals(3)); expect(token.span.end.offset, equals(3)); }); test('throws a StateError if called after end-of-file was consumed', () { var scanner = Scanner('( )'); scanner.next(); scanner.next(); scanner.next(); expect(() => scanner.next(), throwsStateError); }); }); group('scan()', () { test('consumes a matching token and returns true', () { var scanner = Scanner('( )'); expect(scanner.scan(TokenType.leftParen), isTrue); expect(scanner.peek().type, equals(TokenType.rightParen)); }); test("doesn't consume a matching token and returns false", () { var scanner = Scanner('( )'); expect(scanner.scan(TokenType.questionMark), isFalse); expect(scanner.peek().type, equals(TokenType.leftParen)); }); test('throws a StateError called after end-of-file was consumed', () { var scanner = Scanner('( )'); scanner.next(); scanner.next(); scanner.next(); expect(() => scanner.scan(TokenType.endOfFile), throwsStateError); }); }); group('scans a simple token:', () { test('left paren', () => _expectSimpleScan('(', TokenType.leftParen)); test('right paren', () => _expectSimpleScan(')', TokenType.rightParen)); test('or', () => _expectSimpleScan('||', TokenType.or)); test('and', () => _expectSimpleScan('&&', TokenType.and)); test('not', () => _expectSimpleScan('!', TokenType.not)); test('question mark', () => _expectSimpleScan('?', TokenType.questionMark)); test('colon', () => _expectSimpleScan(':', TokenType.colon)); }); group('scans an identifier that', () { test('is simple', () { var token = _scan(' foo '); expect(token, _isIdentifierToken); token as IdentifierToken; // promote token expect(token.name, equals('foo')); expect(token.span.text, equals('foo')); expect(token.span.start.offset, equals(3)); expect(token.span.end.offset, equals(6)); }); test('is a single character', () { var token = _scan('f'); expect(token, _isIdentifierToken); expect((token as IdentifierToken).name, equals('f')); }); test('has a leading underscore', () { var token = _scan('_foo'); expect(token, _isIdentifierToken); expect((token as IdentifierToken).name, equals('_foo')); }); test('has a leading dash', () { var token = _scan('-foo'); expect(token, _isIdentifierToken); expect((token as IdentifierToken).name, equals('-foo')); }); test('contains an underscore', () { var token = _scan('foo_bar'); expect(token, _isIdentifierToken); expect((token as IdentifierToken).name, equals('foo_bar')); }); test('contains a dash', () { var token = _scan('foo-bar'); expect(token, _isIdentifierToken); expect((token as IdentifierToken).name, equals('foo-bar')); }); test('is capitalized', () { var token = _scan('FOO'); expect(token, _isIdentifierToken); expect((token as IdentifierToken).name, equals('FOO')); }); test('contains numbers', () { var token = _scan('foo123'); expect(token, _isIdentifierToken); expect((token as IdentifierToken).name, equals('foo123')); }); }); test('scans an empty selector', () { expect(_scan('').type, equals(TokenType.endOfFile)); }); test('scans multiple tokens', () { var scanner = Scanner('(foo && bar)'); var token = scanner.next(); expect(token.type, equals(TokenType.leftParen)); expect(token.span.start.offset, equals(0)); expect(token.span.end.offset, equals(1)); token = scanner.next(); expect(token.type, equals(TokenType.identifier)); expect((token as IdentifierToken).name, equals('foo')); expect(token.span.start.offset, equals(1)); expect(token.span.end.offset, equals(4)); token = scanner.next(); expect(token.type, equals(TokenType.and)); expect(token.span.start.offset, equals(5)); expect(token.span.end.offset, equals(7)); token = scanner.next(); expect(token.type, equals(TokenType.identifier)); expect((token as IdentifierToken).name, equals('bar')); expect(token.span.start.offset, equals(8)); expect(token.span.end.offset, equals(11)); token = scanner.next(); expect(token.type, equals(TokenType.rightParen)); expect(token.span.start.offset, equals(11)); expect(token.span.end.offset, equals(12)); token = scanner.next(); expect(token.type, equals(TokenType.endOfFile)); expect(token.span.start.offset, equals(12)); expect(token.span.end.offset, equals(12)); }); group('ignores', () { test('a single-line comment', () { var scanner = Scanner('( // &&\n// ||\n)'); expect(scanner.next().type, equals(TokenType.leftParen)); expect(scanner.next().type, equals(TokenType.rightParen)); expect(scanner.next().type, equals(TokenType.endOfFile)); }); test('a single-line comment without a trailing newline', () { var scanner = Scanner('( // &&'); expect(scanner.next().type, equals(TokenType.leftParen)); expect(scanner.next().type, equals(TokenType.endOfFile)); }); test('a multi-line comment', () { var scanner = Scanner('( /* && * /\n|| */\n)'); expect(scanner.next().type, equals(TokenType.leftParen)); expect(scanner.next().type, equals(TokenType.rightParen)); expect(scanner.next().type, equals(TokenType.endOfFile)); }); test('a multi-line nested comment', () { var scanner = Scanner('(/* && /* ? /* || */ : */ ! */)'); expect(scanner.next().type, equals(TokenType.leftParen)); expect(scanner.next().type, equals(TokenType.rightParen)); expect(scanner.next().type, equals(TokenType.endOfFile)); }); test("Dart's notion of whitespace", () { var scanner = Scanner('( \t \n)'); expect(scanner.next().type, equals(TokenType.leftParen)); expect(scanner.next().type, equals(TokenType.rightParen)); expect(scanner.next().type, equals(TokenType.endOfFile)); }); }); group('disallows', () { test('a single |', () { expect(() => _scan('|'), throwsFormatException); }); test('"| |"', () { expect(() => _scan('| |'), throwsFormatException); }); test('a single &', () { expect(() => _scan('&'), throwsFormatException); }); test('"& &"', () { expect(() => _scan('& &'), throwsFormatException); }); test('an unknown operator', () { expect(() => _scan('=='), throwsFormatException); }); test('unicode', () { expect(() => _scan('öh'), throwsFormatException); }); test('an unclosed multi-line comment', () { expect(() => _scan('/*'), throwsFormatException); }); test('an unopened multi-line comment', () { expect(() => _scan('*/'), throwsFormatException); }); }); } /// Asserts that the first token scanned from [selector] has type [type], /// and that that token's span is exactly [selector]. void _expectSimpleScan(String selector, TokenType type) { // Complicate the selector to test that the span covers it correctly. var token = _scan(' $selector '); expect(token.type, equals(type)); expect(token.span.text, equals(selector)); expect(token.span.start.offset, equals(3)); expect(token.span.end.offset, equals(3 + selector.length)); } /// Scans a single token from [selector]. Token _scan(String selector) => Scanner(selector).next();