Values done

This commit is contained in:
thosakwe 2017-07-03 11:37:35 -04:00
parent 96aba898f3
commit 0f68a1c500
18 changed files with 447 additions and 351 deletions

65
.gitignore vendored
View file

@ -26,3 +26,68 @@ doc/api/
# Don't commit pubspec lock file # Don't commit pubspec lock file
# (Library packages only! Remove pattern if developing an application package) # (Library packages only! Remove pattern if developing an application package)
pubspec.lock pubspec.lock
### Dart template
# See https://www.dartlang.org/tools/private-files.html
# Files and directories created by pub
# SDK 1.20 and later (no longer creates packages directories)
# Older SDK versions
# (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20)
# Files created by dart2js
# (Most Dart developers will use pub build to compile Dart, use/modify these
# rules if you intend to use dart2js directly
# Convention is to use extension '.dart.js' for Dart compiled to Javascript to
# differentiate from explicit Javascript files)
# Directory created by dartdoc
# Don't commit pubspec lock file
# (Library packages only! Remove pattern if developing an application package)
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff:
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/dictionaries
# Sensitive or high-churn files:
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.xml
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
# Gradle:
.idea/**/gradle.xml
.idea/**/libraries
# Mongo Explorer plugin:
.idea/**/mongoSettings.xml
## File-based project format:
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties

1
.travis.yml Normal file
View file

@ -0,0 +1 @@
language: dart

View file

@ -1,2 +1,39 @@
# graphql_parser # graphql_parser
[![Pub](https://img.shields.io/pub/v/graphql_parser.svg)](https://pub.dartlang.org/packages/graphql_parser)
[![build status](https://travis-ci.org/thosakwe/graphql_parser.svg)](https://travis-ci.org/thosakwe/graphql_parser)
Parses GraphQL queries and schemas. Parses GraphQL queries and schemas.
*This library is merely a parser*. Any sort of actual GraphQL API functionality must be implemented by you,
or by a third-party package.
[Angel framework](https://angel-dart.github.io)
users should consider
[`package:angel_graphql`](https://pub.dartlang.org/packages/angel_graphql)
as a dead-simple way to add GraphQL functionality to their servers.
# Installation
Add `graphql_parser` as a dependency in your `pubspec.yaml` file:
```yaml
dependencies:
graphql_parser: ^1.0.0
```
# Usage
The AST featured in this library is directly based off this ANTLR4 grammar created by Joseph T. McBride:
https://github.com/antlr/grammars-v4/blob/master/graphql/GraphQL.g4
```dart
import 'package:graphql_parser/graphql_parser.dart';
doSomething(String text) {
var tokens = scan(text);
var parser = new Parser(tokens);
// Parse the GraphQL document using recursive descent
var doc = parser.parseDocument();
// Do something with the parsed GraphQL document...
}
```

View file

@ -1,22 +1,15 @@
import 'dart:async'; import 'dart:async';
import 'package:graphql_parser/src/language/language.dart'; import 'package:graphql_parser/src/language/language.dart';
Stream<String> input() async* { final String INPUT = '''
yield '''
{ {
project(name: "GraphQL") { project(name: "GraphQL") {
tagline tagline
} }
} }
''' '''.trim();
.trim();
}
main() { main() {
var lexer = new Lexer(), parser = new Parser(); var tokens = scan(INPUT);
var stream = input().transform(lexer).asBroadcastStream(); var parser = new Parser(tokens);
stream
..forEach(print)
..pipe(parser);
parser.onNode.forEach(print);
} }

2
lib/graphql_parser.dart Normal file
View file

@ -0,0 +1,2 @@
export 'src/language/ast/ast.dart';
export 'src/language/language.dart';

View file

@ -12,6 +12,9 @@ class ArrayValueContext extends ValueContext {
SourceSpan get span => SourceSpan get span =>
new SourceSpan(LBRACKET.span?.end, RBRACKET.span?.end, toSource()); new SourceSpan(LBRACKET.span?.end, RBRACKET.span?.end, toSource());
@override
List get value => values.map((v) => v.value).toList();
@override @override
String toSource() { String toSource() {
var buf = new StringBuffer('['); var buf = new StringBuffer('[');

View file

@ -3,13 +3,17 @@ import 'package:source_span/src/span.dart';
import 'value.dart'; import 'value.dart';
class BooleanValueContext extends ValueContext { class BooleanValueContext extends ValueContext {
bool _valueCache;
final Token BOOLEAN; final Token BOOLEAN;
BooleanValueContext(this.BOOLEAN) { BooleanValueContext(this.BOOLEAN) {
assert(BOOLEAN?.text == 'true' || BOOLEAN?.text == 'false'); assert(BOOLEAN?.text == 'true' || BOOLEAN?.text == 'false');
} }
bool get booleanValue => BOOLEAN.text == 'true'; bool get booleanValue => _valueCache ??= BOOLEAN.text == 'true';
@override
get value => booleanValue;
@override @override
SourceSpan get span => BOOLEAN.span; SourceSpan get span => BOOLEAN.span;

View file

@ -1,5 +1,6 @@
import 'dart:math' as math;
import 'package:source_span/source_span.dart';
import '../token.dart'; import '../token.dart';
import 'package:source_span/src/span.dart';
import 'value.dart'; import 'value.dart';
class NumberValueContext extends ValueContext { class NumberValueContext extends ValueContext {
@ -7,6 +8,21 @@ class NumberValueContext extends ValueContext {
NumberValueContext(this.NUMBER); NumberValueContext(this.NUMBER);
num get numberValue {
var text = NUMBER.text;
if (!text.contains('E') && !text.contains('e'))
return num.parse(text);
else {
var split = text.split(text.contains('E') ? 'E' : 'e');
var base = num.parse(split[0]);
var exp = num.parse(split[1]);
return base * math.pow(10, exp);
}
}
@override
get value => numberValue;
@override @override
SourceSpan get span => NUMBER.span; SourceSpan get span => NUMBER.span;

View file

@ -1,5 +1,8 @@
import 'dart:convert';
import 'package:charcode/charcode.dart';
import 'package:source_span/source_span.dart';
import '../syntax_error.dart';
import '../token.dart'; import '../token.dart';
import 'package:source_span/src/span.dart';
import 'value.dart'; import 'value.dart';
class StringValueContext extends ValueContext { class StringValueContext extends ValueContext {
@ -10,7 +13,61 @@ class StringValueContext extends ValueContext {
@override @override
SourceSpan get span => STRING.span; SourceSpan get span => STRING.span;
String get stringValue => STRING.text.substring(0, STRING.text.length - 1); String get stringValue {
var text = STRING.text.substring(1, STRING.text.length - 1);
var codeUnits = text.codeUnits;
var buf = new StringBuffer();
for (int i = 0; i < codeUnits.length; i++) {
var ch = codeUnits[i];
if (ch == $backslash) {
if (i < codeUnits.length - 5 && codeUnits[i + 1] == $u) {
var c1 = codeUnits[i += 2],
c2 = codeUnits[++i],
c3 = codeUnits[++i],
c4 = codeUnits[++i];
var hexString = new String.fromCharCodes([c1, c2, c3, c4]);
var hexNumber = int.parse(hexString, radix: 16);
buf.write(new String.fromCharCode(hexNumber));
continue;
}
if (i < codeUnits.length - 1) {
var next = codeUnits[++i];
switch (next) {
case $b:
buf.write('\b');
break;
case $f:
buf.write('\f');
break;
case $n:
buf.writeCharCode($lf);
break;
case $r:
buf.writeCharCode($cr);
break;
case $t:
buf.writeCharCode($tab);
break;
default:
buf.writeCharCode(next);
}
} else
throw new SyntaxError.fromSourceLocation(
'Unexpected "\\" in string literal.', span.start);
} else {
buf.writeCharCode(ch);
}
}
return buf.toString();
}
@override
get value => stringValue;
@override @override
String toSource() => STRING.text; String toSource() => STRING.text;

View file

@ -1,3 +1,5 @@
import 'node.dart'; import 'node.dart';
abstract class ValueContext extends Node {} abstract class ValueContext extends Node {
get value;
}

View file

@ -1,25 +0,0 @@
part of graphql_parser.language.parser;
abstract class BaseParser implements StreamConsumer<Token> {
final StreamController<ArrayValueContext> _onArrayValue =
new StreamController<ArrayValueContext>();
final StreamController<BooleanValueContext> _onBooleanValue =
new StreamController<BooleanValueContext>();
final StreamController<DocumentContext> _onDocument =
new StreamController<DocumentContext>();
final StreamController<Node> _onNode = new StreamController<Node>();
final StreamController<NumberValueContext> _onNumberValue =
new StreamController<NumberValueContext>();
final StreamController<StringValueContext> _onStringValue =
new StreamController<StringValueContext>();
final StreamController<ValueContext> _onValue =
new StreamController<ValueContext>();
Stream<Node> get onArrayValue => _onArrayValue.stream;
Stream<Node> get onBooleanValue => _onBooleanValue.stream;
Stream<Node> get onDocument => _onDocument.stream;
Stream<Node> get onNode => _onNode.stream;
Stream<Node> get onNumberValue => _onNumberValue.stream;
Stream<Node> get onStringValue => _onStringValue.stream;
Stream<Node> get onValue => _onValue.stream;
}

View file

@ -2,5 +2,6 @@ library graphql_parser.language;
export 'lexer.dart'; export 'lexer.dart';
export 'parser.dart'; export 'parser.dart';
export 'syntax_error.dart';
export 'token.dart'; export 'token.dart';
export 'token_type.dart'; export 'token_type.dart';

View file

@ -1,4 +1,3 @@
import 'dart:async';
import 'package:string_scanner/string_scanner.dart'; import 'package:string_scanner/string_scanner.dart';
import 'syntax_error.dart'; import 'syntax_error.dart';
import 'token.dart'; import 'token.dart';
@ -35,43 +34,34 @@ final Map<Pattern, TokenType> _patterns = {
_name: TokenType.NAME _name: TokenType.NAME
}; };
class Lexer implements StreamTransformer<String, Token> { List<Token> scan(String text) {
@override List<Token> out = [];
Stream<Token> bind(Stream<String> stream) { var scanner = new SpanScanner(text);
var ctrl = new StreamController<Token>();
stream.listen((str) { while (!scanner.isDone) {
var scanner = new SpanScanner(str); List<Token> potential = [];
while (!scanner.isDone) { if (scanner.scan(_whitespace)) continue;
List<Token> potential = [];
if (scanner.scan(_whitespace)) continue; for (var pattern in _patterns.keys) {
if (scanner.matches(pattern)) {
for (var pattern in _patterns.keys) { potential.add(new Token(_patterns[pattern], scanner.lastMatch[0], scanner.lastSpan));
if (scanner.matches(pattern)) {
potential.add(new Token(_patterns[pattern], scanner.lastMatch[0]));
}
}
if (potential.isEmpty) {
var ch = new String.fromCharCode(scanner.readChar());
ctrl.addError(new SyntaxError("Unexpected token '$ch'.",
scanner.state.line, scanner.state.column));
} else {
// Choose longest token
potential.sort((a, b) => b.text.length.compareTo(a.text.length));
var chosen = potential.first;
var start = scanner.state;
ctrl.add(chosen);
scanner.scan(chosen.text);
chosen.span = scanner.spanFrom(start);
}
} }
}) }
..onDone(ctrl.close)
..onError(ctrl.addError);
return ctrl.stream; if (potential.isEmpty) {
var ch = new String.fromCharCode(scanner.readChar());
throw new SyntaxError(
"Unexpected token '$ch'.", scanner.state.line, scanner.state.column);
} else {
// Choose longest token
potential.sort((a, b) => b.text.length.compareTo(a.text.length));
var chosen = potential.first;
var start = scanner.state;
out.add(chosen);
scanner.scan(chosen.text);
}
} }
return out;
} }

View file

@ -1,180 +1,187 @@
library graphql_parser.language.parser; library graphql_parser.language.parser;
import 'dart:async';
import 'ast/ast.dart'; import 'ast/ast.dart';
import 'stream_reader.dart';
import 'syntax_error.dart'; import 'syntax_error.dart';
import 'token.dart'; import 'token.dart';
import 'token_type.dart'; import 'token_type.dart';
part 'base_parser.dart';
class Parser extends BaseParser { class Parser {
bool _closed = false; Token _current;
final Completer _closer = new Completer();
final List<SyntaxError> _errors = []; final List<SyntaxError> _errors = [];
final StreamReader<Token> _reader = new StreamReader(); int _index = -1;
final List<Token> tokens;
Parser(this.tokens);
Token get current => _current;
List<SyntaxError> get errors => new List<SyntaxError>.unmodifiable(_errors); List<SyntaxError> get errors => new List<SyntaxError>.unmodifiable(_errors);
Future _waterfall(List<Function> futures) async { bool next(TokenType type) {
for (var f in futures) { if (peek()?.type == type) {
var r = await f(); _current = tokens[++_index];
if (r != null) return r;
}
}
@override
Future addStream(Stream<Token> stream) {
if (_closed) throw new StateError('Parser is already closed.');
_closed = true;
_reader.onData
.listen((data) => _waterfall([parseDocument, parseBooleanValue]))
..onDone(() => Future.wait([
_onBooleanValue.close(),
_onDocument.close(),
_onNode.close(),
]))
..onError(_closer.completeError);
return stream.pipe(_reader);
}
@override
Future close() {
return _closer.future;
}
Future<bool> expect(TokenType type) async {
var peek = await _reader.peek();
if (peek?.type != type) {
_errors.add(new SyntaxError.fromSourceLocation(
"Expected $type, found '${peek?.text ?? 'empty text'}' instead.",
peek?.span?.start));
return false;
} else {
await _reader.consume();
return true;
}
}
Future<bool> maybe(TokenType type) async {
var peek = await _reader.peek();
if (peek?.type == type) {
await _reader.consume();
return true; return true;
} }
return false; return false;
} }
Future<bool> nextIs(TokenType type) => Token peek() {
_reader.peek().then((t) => t?.type == type); if (_index < tokens.length - 1) {
return tokens[_index + 1];
Future<DocumentContext> parseDocument() async {
return null;
}
Future<ValueContext> parseValue() async {
ValueContext value;
var string = await parseStringValue();
if (string != null)
value = string;
else {
var number = await parseNumberValue();
if (number != null)
value = number;
else {
var boolean = await parseBooleanValue();
if (boolean != null)
value = boolean;
else {
var array = await parseArrayValue();
if (array != null) value = array;
}
}
}
if (value != null) _onValue.add(value);
return value;
}
Future<StringValueContext> parseStringValue() async {
if (await nextIs(TokenType.STRING)) {
var result = new StringValueContext(await _reader.consume());
_onStringValue.add(result);
return result;
} }
return null; return null;
} }
Future<NumberValueContext> parseNumberValue() async { DocumentContext parseDocument() {}
if (await nextIs(TokenType.NUMBER)) {
var result = new NumberValueContext(await _reader.consume());
_onNumberValue.add(result);
return result;
}
return null; FragmentDefinitionContext parseFragmentDefinition() {}
}
Future<BooleanValueContext> parseBooleanValue() async { FragmentSpreadContext parseFragmentSpread() {}
if (await nextIs(TokenType.BOOLEAN)) {
var result = new BooleanValueContext(await _reader.consume());
_onBooleanValue.add(result);
return result;
}
return null; InlineFragmentContext parseInlineFragment() {}
}
Future<ArrayValueContext> parseArrayValue() async { SelectionSetContext parseSelectionSet() {}
if (await nextIs(TokenType.LBRACKET)) {
ArrayValueContext result;
var LBRACKET = await _reader.consume();
List<ValueContext> values = [];
if (await nextIs(TokenType.RBRACKET)) { SelectionContext parseSelection() {}
result = new ArrayValueContext(LBRACKET, await _reader.consume());
_onArrayValue.add(result);
return result;
}
while (!_reader.isDone) { FieldContext parseField() {}
ValueContext value = await parseValue();
if (value == null) break;
values.add(value); FieldNameContext parseFieldName() {}
if (await nextIs(TokenType.COMMA)) { AliasContext parseAlias() {}
await _reader.consume();
continue;
} else if (await nextIs(TokenType.RBRACKET)) {
result = new ArrayValueContext(LBRACKET, await _reader.consume());
_onArrayValue.add(result);
return result;
}
VariableDefinitionsContext parseVariableDefinitions() {}
VariableDefinitionContext parseVariableDefinition() {}
List<DirectiveContext> parseDirectives() {}
DirectiveContext parseDirective() {
if (next(TokenType.ARROBA)) {
var ARROBA = current;
if (next(TokenType.NAME)) {
var NAME = current;
if (next(TokenType.COLON)) {
var COLON = current;
var val = parseValueOrVariable();
if (val != null)
return new DirectiveContext(
ARROBA, NAME, COLON, null, null, null, val);
else
throw new SyntaxError.fromSourceLocation(
'Expected value or variable in directive after colon.',
COLON.span.end);
} else if (next(TokenType.LPAREN)) {
var LPAREN = current;
var arg = parseArgument();
if (arg != null) {
if (next(TokenType.RPAREN)) {
return new DirectiveContext(
ARROBA, NAME, null, LPAREN, current, arg, null);
} else
throw new SyntaxError.fromSourceLocation(
'Expected \'(\'', arg.valueOrVariable.span.end);
} else
throw new SyntaxError.fromSourceLocation(
'Expected argument in directive.', LPAREN.span.end);
} else
return new DirectiveContext(
ARROBA, NAME, null, null, null, null, null);
} else
throw new SyntaxError.fromSourceLocation( throw new SyntaxError.fromSourceLocation(
'Expected comma or right bracket in array', 'Expected name for directive.', ARROBA.span.end);
(await _reader.current())?.span?.start); } else
return null;
}
ArgumentContext parseArgument() {
if (next(TokenType.NAME)) {
var NAME = current;
if (next(TokenType.COLON)) {
var COLON = current;
var val = parseValueOrVariable();
if (val != null)
return new ArgumentContext(NAME, COLON, val);
else
throw new SyntaxError.fromSourceLocation(
'Expected value or variable in argument.', COLON.span.end);
} else
throw new SyntaxError.fromSourceLocation(
'Expected colon after name in argument.', NAME.span.end);
} else
return null;
}
ValueOrVariableContext parseValueOrVariable() {
var value = parseValue();
if (value != null)
return new ValueOrVariableContext(value, null);
else {
var variable = parseVariable();
if (variable != null)
return new ValueOrVariableContext(null, variable);
else
return null;
}
}
VariableContext parseVariable() {
if (next(TokenType.DOLLAR)) {
var DOLLAR = current;
if (next(TokenType.NAME))
return new VariableContext(DOLLAR, current);
else
throw new SyntaxError.fromSourceLocation(
'Expected name for variable; found a lone "\$" instead.',
DOLLAR.span.end);
} else
return null;
}
DefaultValueContext parseDefaultValue() {}
TypeConditionContext parseTypeCondition() {}
ValueContext parseValue() {
return parseStringValue() ??
parseNumberValue() ??
parseBooleanValue() ??
parseArrayValue();
}
StringValueContext parseStringValue() =>
next(TokenType.STRING) ? new StringValueContext(current) : null;
NumberValueContext parseNumberValue() =>
next(TokenType.NUMBER) ? new NumberValueContext(current) : null;
BooleanValueContext parseBooleanValue() =>
next(TokenType.BOOLEAN) ? new BooleanValueContext(current) : null;
ArrayValueContext parseArrayValue() {
if (next(TokenType.LBRACKET)) {
var LBRACKET = current;
List<ValueContext> values = [];
ValueContext value = parseValue();
while (value != null) {
values.add(value);
if (next(TokenType.COMMA)) {
value = parseValue();
} else
break;
} }
throw new SyntaxError.fromSourceLocation( if (next(TokenType.RBRACKET)) {
'Unterminated array literal.', LBRACKET.span?.start); return new ArrayValueContext(LBRACKET, current)..values.addAll(values);
} } else
throw new SyntaxError.fromSourceLocation(
if (await nextIs(TokenType.BOOLEAN)) { 'Unterminated array literal.', LBRACKET.span.end);
var result = new BooleanValueContext(await _reader.consume()); } else
_onBooleanValue.add(result); return null;
return result;
}
return null;
} }
} }

View file

@ -1,125 +0,0 @@
import 'dart:async';
import 'dart:collection';
class StreamReader<T> implements StreamConsumer<T> {
final Queue<T> _buffer = new Queue();
bool _closed = false;
T _current;
bool _onDataListening = false;
final Queue<Completer<T>> _nextQueue = new Queue();
final Queue<Completer<T>> _peekQueue = new Queue();
StreamController<T> _onData;
bool get isDone => _closed;
Stream<T> get onData => _onData.stream;
_onListen() {
_onDataListening = true;
}
_onPause() {
_onDataListening = false;
}
StreamReader() {
_onData = new StreamController<T>(
onListen: _onListen,
onResume: _onListen,
onPause: _onPause,
onCancel: _onPause);
}
Future<T> current() {
if (_current == null) {
if (_nextQueue.isNotEmpty) return _nextQueue.first.future;
return consume();
}
return new Future.value(_current);
}
Future<T> peek() {
if (isDone) throw new StateError('Cannot read from closed stream.');
if (_buffer.isNotEmpty) return new Future.value(_buffer.first);
var c = new Completer<T>();
_peekQueue.addLast(c);
return c.future;
}
Future<T> consume() {
if (isDone) throw new StateError('Cannot read from closed stream.');
if (_buffer.isNotEmpty) {
_current = _buffer.removeFirst();
return close().then((_) => new Future.value(_current));
}
var c = new Completer<T>();
_nextQueue.addLast(c);
return c.future;
}
@override
Future addStream(Stream<T> stream) {
if (_closed) throw new StateError('StreamReader has already been used.');
var c = new Completer();
stream.listen((data) {
if (_onDataListening) _onData.add(data);
if (_peekQueue.isNotEmpty || _nextQueue.isNotEmpty) {
if (_peekQueue.isNotEmpty) {
_peekQueue.removeFirst().complete(data);
}
if (_nextQueue.isNotEmpty) {
_nextQueue.removeFirst().complete(_current = data);
}
} else {
_buffer.add(data);
}
})
..onDone(() => close().then(c.complete))
..onError(c.completeError);
return c.future;
}
@override
Future close() async {
if (_buffer.isEmpty && _nextQueue.isEmpty && _peekQueue.isEmpty) {
_closed = true;
kill(Completer c) {
c.completeError(new StateError(
'Reached end of stream, although more input was expected.'));
}
_peekQueue.forEach(kill);
_nextQueue.forEach(kill);
}
}
}
class _IteratorReader<T> {
final Iterator<T> _tokens;
T _current;
_IteratorReader(this._tokens) {
_tokens.moveNext();
}
T advance() {
_current = _tokens.current;
_tokens.moveNext();
return _current;
}
bool get eof => _tokens.current == null;
T peek() => _tokens.current;
}

View file

@ -1,8 +1,12 @@
author: "Tobe O" name: graphql_parser
description: "Parses GraphQL queries and schemas." version: 0.0.0
homepage: "https://github.com/thosakwe/graphql_parser" description: Parses GraphQL queries and schemas.
name: "graphql_parser" author: Tobe O <thosakwe@gmail.com>
version: "0.0.0" homepage: https://github.com/thosakwe/graphql_parser
environment:
sdk: ">=1.19.0"
dependencies: dependencies:
source_span: "^1.3.1" source_span: ^1.0.0
string_scanner: "^1.0.1" string_scanner: ^1.0.0
dev_dependencies:
test: ^0.12.0

23
test/common.dart Normal file
View file

@ -0,0 +1,23 @@
import 'package:graphql_parser/graphql_parser.dart';
import 'package:matcher/matcher.dart';
Parser parse(String text) => new Parser(scan(text));
Matcher equalsParsed(value) => new _EqualsParsed(value);
class _EqualsParsed extends Matcher {
final value;
_EqualsParsed(this.value);
@override
Description describe(Description description)
=> description.add('equals $value when parsed as a GraphQL value');
@override
bool matches(String item, Map matchState) {
var p = parse(item);
var v = p.parseValue();
return equals(value).matches(v.value, matchState);
}
}

41
test/value_test.dart Normal file
View file

@ -0,0 +1,41 @@
import 'dart:math' as math;
import 'package:test/test.dart';
import 'common.dart';
main() {
test('boolean', () {
expect('true', equalsParsed(true));
expect('false', equalsParsed(false));
});
test('number', () {
expect('1', equalsParsed(1));
expect('1.0', equalsParsed(1.0));
expect('-1', equalsParsed(-1));
expect('-1.0', equalsParsed(-1.0));
expect('6.26e-34', equalsParsed(6.26 * math.pow(10, -34)));
expect('-6.26e-34', equalsParsed(-6.26 * math.pow(10, -34)));
expect('-6.26e34', equalsParsed(-6.26 * math.pow(10, 34)));
});
test('array', () {
expect('[]', equalsParsed([]));
expect('[1,2]', equalsParsed([1,2]));
expect('[1,2, 3]', equalsParsed([1,2,3]));
expect('["a"]', equalsParsed(['a']));
});
test('string', () {
expect('""', equalsParsed(''));
expect('"a"', equalsParsed('a'));
expect('"abc"', equalsParsed('abc'));
expect('"\\""', equalsParsed('"'));
expect('"\\b"', equalsParsed('\b'));
expect('"\\f"', equalsParsed('\f'));
expect('"\\n"', equalsParsed('\n'));
expect('"\\r"', equalsParsed('\r'));
expect('"\\t"', equalsParsed('\t'));
expect('"\\u0123"', equalsParsed('\u0123'));
expect('"\\u0123\\u4567"', equalsParsed('\u0123\u4567'));
});
}