"use strict";

const should = require("should");

const RuleSet = require("../lib/RuleSet");

function match(ruleSet, resource) {
	const result = ruleSet.exec({
		resource: resource
	});
	return result.filter((r) => {
		return r.type === "use";
	}).map(r => r.value).map(r => {
		if(!r.options)
			return r.loader;
		if(typeof r.options === "string")
			return r.loader + "?" + r.options;
		return r.loader + "?" + JSON.stringify(r.options);
	});
}

describe("RuleSet", () => {
	it("should create RuleSet with a blank array", () => {
		const loader = new RuleSet([]);
		(loader.rules).should.eql([]);
	});
	it("should create RuleSet and match with empty array", () => {
		const loader = new RuleSet([]);
		(match(loader, "something")).should.eql([]);
	});
	it("should not match with loaders array", () => {
		const loader = new RuleSet([{
			test: /\.css$/,
			loader: "css"
		}]);
		(match(loader, "something")).should.eql([]);
	});

	it("should match with regex", () => {
		const loader = new RuleSet([{
			test: /\.css$/,
			loader: "css"
		}]);
		(match(loader, "style.css")).should.eql(["css"]);
	});

	it("should match with string", () => {
		const loader = new RuleSet([{
			test: "style.css",
			loader: "css"
		}]);
		(match(loader, "style.css")).should.eql(["css"]);
	});

	it("should match with function", () => {
		const loader = new RuleSet([{
			test: function(str) {
				return str === "style.css";
			},
			loader: "css"
		}]);
		(match(loader, "style.css")).should.eql(["css"]);
	});

	it("should throw if invalid test", () => {
		should.throws(() => {
			const loader = new RuleSet([{
				test: {
					invalid: "test"
				},
				loader: "css"
			}]);
			(match(loader, "style.css")).should.eql(["css"]);
		}, /Unexcepted property invalid in condition/);
	});

	it("should accept multiple test array that all match", () => {
		const loader = new RuleSet([{
			test: [
				/style.css/,
				/yle.css/
			],
			loader: "css"
		}]);
		(match(loader, "style.css")).should.eql(["css"]);
	});

	it("should accept multiple test array that not all match", () => {
		const loader = new RuleSet([{
			test: [
				/style.css/,
				/something.css/
			],
			loader: "css"
		}]);
		(match(loader, "style.css")).should.eql(["css"]);
	});

	it("should not match if include does not match", () => {
		const loader = new RuleSet([{
			test: /\.css$/,
			include: /output.css/,
			loader: "css"
		}]);
		(match(loader, "style.css")).should.eql([]);
	});

	it("should match if include matches", () => {
		const loader = new RuleSet([{
			test: /\.css$/,
			include: /style.css/,
			loader: "css"
		}]);
		(match(loader, "style.css")).should.eql(["css"]);
	});

	it("should not match if exclude matches", () => {
		const loader = new RuleSet([{
			test: /\.css$/,
			exclude: /style.css/,
			loader: "css"
		}]);
		(match(loader, "style.css")).should.eql([]);
	});

	it("should match if exclude does not match", () => {
		const loader = new RuleSet([{
			test: /\.css$/,
			exclude: /output.css/,
			loader: "css"
		}]);
		(match(loader, "style.css")).should.eql(["css"]);
	});

	it("should work if a loader is applied to all files", () => {
		const loader = new RuleSet([{
			loader: "css"
		}]);
		(match(loader, "style.css")).should.eql(["css"]);
		(match(loader, "scripts.js")).should.eql(["css"]);
	});

	it("should work with using loader as string", () => {
		const loader = new RuleSet([{
			test: /\.css$/,
			loader: "css"
		}]);
		(match(loader, "style.css")).should.eql(["css"]);
	});

	it("should work with using loader as array", () => {
		const loader = new RuleSet([{
			test: /\.css$/,
			loader: ["css"]
		}]);
		(match(loader, "style.css")).should.eql(["css"]);
	});

	it("should work with using loaders as string", () => {
		const loader = new RuleSet([{
			test: /\.css$/,
			loaders: "css"
		}]);
		(match(loader, "style.css")).should.eql(["css"]);
	});

	it("should work with using loaders as array", () => {
		const loader = new RuleSet([{
			test: /\.css$/,
			loaders: ["css"]
		}]);
		(match(loader, "style.css")).should.eql(["css"]);
	});

	it("should throw if using loaders with non-string or array", () => {
		should.throws(function() {
			const loader = new RuleSet([{
				test: /\.css$/,
				loaders: {
					someObj: true
				}
			}]);
			(match(loader, "style.css")).should.eql(["css"]);
		}, /No loader specified/);
	});

	it("should work with using loader with inline query", () => {
		const loader = new RuleSet([{
			test: /\.css$/,
			loader: "css?modules=1"
		}]);
		(match(loader, "style.css")).should.eql(["css?modules=1"]);
	});

	it("should work with using loader with string query", () => {
		const loader = new RuleSet([{
			test: /\.css$/,
			loader: "css",
			query: "modules=1"
		}]);
		(match(loader, "style.css")).should.eql(["css?modules=1"]);
	});

	it("should work with using loader with object query", () => {
		const loader = new RuleSet([{
			test: /\.css$/,
			loader: "css",
			query: {
				modules: 1
			}
		}]);
		(match(loader, "style.css")).should.eql(["css?{\"modules\":1}"]);
	});

	it("should work with using array loaders with basic object notation", () => {
		const loader = new RuleSet([{
			test: /\.css$/,
			loaders: [{
				loader: "css"
			}]
		}]);
		(match(loader, "style.css")).should.eql(["css"]);
	});

	it("should throw if using array loaders with object notation without specifying a loader", () => {
		should.throws(() => {
			const loader = new RuleSet([{
				test: /\.css$/,
				loaders: [{
					stuff: 1
				}]
			}]);
			match(loader, "style.css");
		}, /No loader specified/);
	});

	it("should work with using array loaders with object notation", () => {
		const loader = new RuleSet([{
			test: /\.css$/,
			loaders: [{
				loader: "css",
				query: "modules=1"
			}]
		}]);
		(match(loader, "style.css")).should.eql(["css?modules=1"]);
	});

	it("should work with using multiple array loaders with object notation", () => {
		const loader = new RuleSet([{
			test: /\.css$/,
			loaders: [{
				loader: "style",
				query: "filesize=1000"
			}, {
				loader: "css",
				query: "modules=1"
			}]
		}]);
		(match(loader, "style.css")).should.eql(["style?filesize=1000", "css?modules=1"]);
	});

	it("should work with using string multiple loaders", () => {
		const loader = new RuleSet([{
			test: /\.css$/,
			loaders: "style?filesize=1000!css?modules=1"
		}]);
		(match(loader, "style.css")).should.eql(["style?filesize=1000", "css?modules=1"]);
	});

	it("should throw if using array loaders with a single legacy", () => {
		should.throws(() => {
			const loader = new RuleSet([{
				test: /\.css$/,
				loaders: ["style-loader", "css-loader"],
				query: "modules=1"
			}]);
			(match(loader, "style.css")).should.eql(["css"]);
		}, /options\/query cannot be used with loaders/);
	});

	it("should work when using array loaders", () => {
		const loader = new RuleSet([{
			test: /\.css$/,
			loaders: ["style-loader", "css-loader"]
		}]);
		(match(loader, "style.css")).should.eql(["style-loader", "css-loader"]);
	});

	it("should work when using an array of functions returning a loader", () => {
		const loader = new RuleSet([{
			test: /\.css$/,
			use: [
				function(data) {
					return {
						loader: "style-loader"
					};
				},
				function(data) {
					return {
						loader: "css-loader"
					};
				},
			]
		}]);
		(match(loader, "style.css")).should.eql(["style-loader", "css-loader"]);
	});

	it("should work when using an array of either functions or strings returning a loader", () => {
		const loader = new RuleSet([{
			test: /\.css$/,
			use: [
				"style-loader",
				function(data) {
					return {
						loader: "css-loader"
					};
				},
			]
		}]);
		(match(loader, "style.css")).should.eql(["style-loader", "css-loader"]);
	});

	it("should work when using an array of functions returning either a loader obejct or loader name string", () => {
		const loader = new RuleSet([{
			test: /\.css$/,
			use: [
				function(data) {
					return "style-loader"
				},
				function(data) {
					return {
						loader: "css-loader"
					};
				},
			]
		}]);
		(match(loader, "style.css")).should.eql(["style-loader", "css-loader"]);
	});

	it("should throw if using array loaders with invalid type", () => {
		should.throws(() => {
			const loader = new RuleSet([{
				test: /\.css$/,
				loaders: ["style-loader", "css-loader", 5],
			}]);
			(match(loader, "style.css")).should.eql(["css"]);
		}, /No loader specified/);
	});

	describe("when exclude array holds an undefined item", () => {
		function errorHasContext(err) {
			if(/Expected condition but got falsy value/.test(err) &&
				/test/.test(err) &&
				/include/.test(err) &&
				/exclude/.test(err) &&
				/node_modules/.test(err) &&
				/undefined/.test(err)) {
				return true;
			}
		}

		it("should throw with context", () => {
			should.throws(() => {
				const loader = new RuleSet([{
					test: /\.css$/,
					loader: "css",
					include: [
						"src",
					],
					exclude: [
						"node_modules",
						undefined,
					],
				}]);
				(match(loader, "style.css")).should.eql(["css"]);
			}, errorHasContext);
		});
		it("in resource should throw with context", () => {
			should.throws(() => {
				const loader = new RuleSet([{
					resource: {
						test: /\.css$/,
						include: [
							"src",
						],
						exclude: [
							"node_modules",
							undefined,
						],
					},
				}]);
				(match(loader, "style.css")).should.eql(["css"]);
			}, errorHasContext);
		});

		it("in issuer should throw with context", () => {
			should.throws(() => {
				const loader = new RuleSet([{
					issuer: {
						test: /\.css$/,
						include: [
							"src",
						],
						exclude: [
							"node_modules",
							undefined,
						],
					},
				}]);
				(match(loader, "style.css")).should.eql(["css"]);
			}, errorHasContext);
		});
	});
});
