#include <iostream>
#include <vector>
#include <array>
#include <list>
#include <string>
#include <stack>
#include <queue>
#include <deque>
#include <map>
#include <unordered_map>
#include <set>
#include <unordered_set>
#include <tuple>
#include <bitset>
#include <memory>
#include <cmath>
#include <algorithm>
#include <functional>
#include <iomanip>
#include <numeric>
#include <climits>
#include <cfloat>
#include <cassert>
#include <random>
int sign(const int value) {
	if (value < 0) return -1;
	else if (value == 0) return 0;
	else return 1;
}
struct Point {
	int x, y;
};
struct Vector {
	int dx, dy;
	Vector(const Point from, const Point to) : dx{ to.x - from.x }, dy{ to.y - from.y }{};
	int cross(const Vector that) const {
		return dx * that.dy - dy * that.dx;
	}
	int dot(const Vector that) const {
		return dx * that.dx + dy * that.dy;
	}
};
struct Edge {
	Point from, to;
	bool is_cross(const Edge that) const {
		auto this_vec = Vector(from, to);
		auto that_vec = Vector(that.from, that.to);
		if (is_parallel(that)) {
			if (this_vec.cross(Vector(from, that.to)) != 0) return false;
			return Vector(from, that.to).dot(Vector(to, that.to)) <= 0 ||
				Vector(from, that.from).dot(Vector(to, that.from)) <= 0 ||
				Vector(that.from, to).dot(Vector(that.to, to)) <= 0 ||
				Vector(that.from, from).dot(Vector(that.to, from)) <= 0;
		}
		return sign(this_vec.cross(Vector(from, that.from))) * sign(this_vec.cross(Vector(from, that.to))) <= 0 &&
			sign(that_vec.cross(Vector(that.from, from))) * sign(that_vec.cross(Vector(that.from, to))) <= 0;
	}
	bool is_parallel(const Edge that) const {
		return (to.x - from.x) * (that.to.y - that.from.y) == (to.y - from.y) * (that.to.x - that.from.x);
	}
};
struct Square {
	std::vector<Point> points;
	Square() { points.reserve(4); }
	bool contains(const Point that) const {
		for (auto i = 0; i < points.size(); ++i) {
			if (Vector(points[i], points[(i + 1) % points.size()]).cross(Vector(points[i], that)) > 0) return false;
		}
		return true;
	}
	bool has_intersection(const Square& that) const {
		if (std::any_of(points.begin(), points.end(), [&that](const Point p) {return that.contains(p); })) return true;
		if (std::any_of(that.points.begin(), that.points.end(), [this](const Point p) {return this->contains(p); })) return true;
		for (auto i = 0; i < points.size(); ++i) {
			auto edge = Edge{ points[i], points[(i + 1) % points.size()] };
			for (auto j = 0; j < that.points.size(); ++j) {
				if (edge.is_cross(Edge{ that.points[j], that.points[(j + 1) % that.points.size()] })) return true;
			}
		}
		return false;
	}
};
class UnionFind {
	mutable std::vector<int> vec;
public:
	UnionFind(const int size) : vec(size, -1) {};
	int find(int a) const {
		if (vec[a] < 0) return a;
		else return vec[a] = find(vec[a]);
	}
	void unite(int a, int b) {
		a = find(a);
		b = find(b);
		if (a != b) {
			if (vec[a] < vec[b]) {
				vec[a] += vec[b];
				vec[b] = a;
			}
			else {
				vec[b] += vec[a];
				vec[a] = b;
			}
		}
	}
};
int main() {
	while (true) {
		int n; std::cin >> n; if (n == 0) break;
		for (auto _i = 0; _i < n; ++_i) {
			int m; std::cin >> m;
			std::vector<Square> squares(m);
			for (auto i = 0; i < m; ++i) {
				for (auto j = 0; j < 4; ++j) {
					Point p; std::cin >> p.x >> p.y;
					squares[i].points.push_back(p);
				}
			}
			UnionFind uft(m);
			for (auto i = 0; i < m; ++i) {
				for (auto j = 0; j < i; ++j) {
					if (squares[i].has_intersection(squares[j])) {
						uft.unite(i, j);
					}
				}
			}
			int count = 0;
			for (auto i = 0; i < m; ++i) if (uft.find(i) == i) ++count;
			std::cout << count << '\n';
		}
	}
}


Measured memory_bytes: 3024