#define _USE_MATH_DEFINES
#include <iostream>
#include <complex>
#include <algorithm>
#include <vector>
#include <stack>
#include <string>
#include <queue>
#include <cmath>
#include <math.h>
#include <numeric>
#include <list>
#include <sstream>
#include <fstream>
#include <iomanip>
#include <climits>
#include <set>
#include <memory.h>
#include <memory>
#include <cstdio>
#include <cstdlib>
#include <cctype>
#include <cassert>
#include <map>
#include <cassert>
#include <time.h>
#include <ctime>

using namespace std;

typedef complex<double> xy_t;
typedef pair<xy_t, xy_t> line;
typedef vector<xy_t> polygon;
typedef long long ll;
typedef pair<int, int> P;
typedef pair<int , P> PP;
typedef pair<int, string> Ps;
typedef vector<int> vec;
typedef vector<vec> mat;
const int INF = 1 << 29;
const double EPS = 1e-10;
const double PI = 3.1415926535897932384626433832795;
const int CLK = CLOCKS_PER_SEC;

#define rep(i, n) for(int i = 0; i < n; i++)
#define rep2(i, m, n) for(int i = m; i < n; i++)
#define repD(i, n) for(int i = n; i >= 0; i--)

double cross(const xy_t &a, const xy_t &b){
	return imag(conj(a) * b);
}

double dot(const xy_t &a, const xy_t &b){
	return real(conj(a) * b);
}

int ccw(xy_t a, xy_t b, xy_t c){
	b-=a, c-=a;
	if(cross(b, c) > 0) return 1;
	if(cross(b, c) < 0) return -1;
	if(dot(b, c) < 0) return 2;
	if(norm(b) < norm(c)) return -2;
	return 0;
}

bool intersectSS(const line &l, const line &m){
	return ccw(l.first, l.second, m.first) * ccw(l.first, l.second, m.second) <= 0 &&
		ccw(m.first, m.second, l.first) * ccw(m.first, m.second, l.second) <= 0;
}

bool intersectPOP(const polygon &pol, const xy_t &p){
	bool in = false;
	int n = pol.size();
	line l = line(p, xy_t(1e10, p.imag()));
	/*
	rep(i, n){
		xy_t a = pol[i] - p, b = pol[(i+1)%n] - p;
		if(imag(a) > imag(b)) swap(a, b);
		if(imag(a) < EPS && 0 < imag(b) && cross(a, b) < 0) in = !in;
		if(abs(cross(a, b)) < EPS && dot(a, b) < EPS) return true;
	}
	*/
	rep(i, n){
		line lp = line(pol[i], pol[(i+1)%n]);
		if(intersectSS(lp, line(p, p))) return true;
		if(intersectSS(lp, l) && abs(p.imag() - max(lp.first.imag(), lp.second.imag())) > 0) in = !in;
	}
	return in;
}

int intersectPOPO(const polygon &p1, const polygon &p2){
	int n = p1.size();
	int m = p2.size();
	rep(i, n) rep(j, m) if(intersectSS(line(p1[i], p1[(i+1)%n]), line(p2[j], p2[(j+1)%m]))) return 1;
	rep(i, n) if(intersectPOP(p2, p1[i])) return 2;
	rep(i, m) if(intersectPOP(p1, p2[i])) return 3;
	return 0;
}

polygon pols[200];
bool g[200][200];
bool used[200];

void dfs(int v){
	used[v] = true;
	rep(i, 200){
		if(g[v][i] && !used[i]){
			used[i] = true;
			dfs(i);
		}
	}
}

int main(){
	int n, m;
	int x, y;
	while(cin >> n && n){
		rep(k, n){
			cin >> m;
			memset(g, false, sizeof(g));
			memset(used, false, sizeof(used));
			rep(i, m){
				rep(j, 4){
					cin >> x >> y;
					pols[i].push_back(xy_t(x, y));
				}
			}
				
			rep(i, m){
				rep(j, m){
					if(intersectPOPO(pols[i], pols[j])){
						g[i][j] = true;
						g[j][i] = true;
					}
				}
			}
			int res = 0;
			rep(i, m) if(!used[i]){
				res++;
				dfs(i);
			}
			cout << res << endl;
			rep(i, 200) pols[i].clear();
		}
	}
	return 0;
}

Measured memory_bytes: 976